Java.lang.String son olmasaydı gerçekten ne olurdu?


16

Uzun zamandır Java geliştiricisiyim ve son olarak, uzmanlaştıktan sonra, sertifika sınavına girmek için terbiyeli bir şekilde çalışmak için zamanım var ... Her zaman beni rahatsız eden bir şey String "final" dir. Güvenlik konularını ve ilgili şeyleri okuduğumda anlıyorum ... Ama, cidden, bunun gerçek bir örneği var mı?

Örneğin, String son olmasaydı ne olurdu? Sanki Ruby'de değil. Ruby topluluğundan gelen herhangi bir şikayet duymadım ... Ve StringUtils'in ve ilgili sınıfların ya kendiniz uygulamanız ya da sadece bu davranışı uygulamak için web'de arama yapmanız gerektiğinin farkındayım (4 kod satırı) istiyoruz.


9
Bu soruya Stack Overflow ile ilgilenebilirsiniz: stackoverflow.com/questions/2068804/why-is-string-final-in-java
Thomas Owens

Benzer şekilde, ne olur java.io.Filedeğildi final. Oh, değil. Kahretsin.
Tom Hawtin - tackline

1
Batı medeniyetinin sonu bildiğimiz gibi.
Tulains Córdova

Yanıtlar:


22

Ana neden hızdır: finalsınıflar genişletilemez, bu da JIT'in dizeleri işlerken her türlü optimizasyonu yapmasına izin vermez - geçersiz kılınan yöntemleri kontrol etmeye asla gerek yoktur.

Diğer bir neden iplik güvenliğidir: Dokunulmazlar her zaman iplik güvenlidir, çünkü bir iplik bir başkasına geçmeden önce bunları tamamen inşa etmek zorundadır - ve binadan sonra artık değiştirilemezler.

Ayrıca, Java çalışma zamanının mucitleri her zaman güvenlik tarafında hata yapmak istediler. Ne yaptığınızı bilmiyorsanız, String'i (Groovy'de sık sık yaptığım bir şey olduğu için) genişletmek, bir kutu solucan açabilir.


2
Yanlış bir cevap. JVM spesifikasyonunun gerektirdiği doğrulama dışında, HotSpot sadece finalkod derlenirken bir yöntemin geçersiz kılınmadığını hızlı bir kontrol olarak kullanır .
Tom Hawtin - tackline

1
@ TomHawtin-tackline: Referanslar?
Aaron Digulla

1
Değişmezliğin ve nihai olmanın bir şekilde ilişkili olduğunu ima ediyorsunuz. Msgstr "Başka bir neden diş güvenliğidir: Dokunulmazlar her zaman diş güvenli ...". Bir nesnenin değişmez olmasının veya olmamasının nedeni, nihai veya kesin olmadığından dolayı değildir.
Tulains Córdova

1
Ayrıca, String sınıfı , sınıftaki finalanahtar kelimeden bağımsız olarak değiştirilemez . İçerdiği karakter dizisi olan o değişmez hale nihai. Sınıfın kendisinin son haline getirilmesi, değiştirilebilir bir alt sınıf eklenemediğinden, döngüyü @abl'den bahsedildiği gibi kapatır.

1
@ Kesin olarak, kesin olan karakter dizisi dizinin içeriğini değiştiremez, dizinin içeriğini değiştiremez kılar.
Michał Kosmulski

10

Java Stringsınıfının kesin olması için başka bir neden daha var : bazı senaryolarda güvenlik için önemlidir. Eğer Stringsınıfı son değildi, şu kod kötü niyetli bir arayan tarafından ince saldırılara karşı savunmasız olacaktır:

 public String makeSafeLink(String url, String text) {
     if (!url.startsWith("http:") && !url.startsWith("https:"))
         throw SecurityException("only http/https URLs are allowed");
     return "<a href=\"" + escape(url) + "\">" + escape(text) + "</a>";
 }

Saldırı: Kötü amaçlı bir arayan, String'in alt sınıfını oluşturabilir EvilString, burada EvilString.startsWith()her zaman doğru döner , ancak değerinin EvilStringkötü bir şey olduğu (ör.javascript:alert('xss') .). Alt sınıflama nedeniyle, bu güvenlik kontrolünden kaçınır. Bu saldırı, kullanım zamanı kontrol (TOCTTOU) güvenlik açığı olarak bilinir: kontrolün yapıldığı zaman (url'nin http / https ile başladığı zaman) ve değerin kullanıldığı zaman arasında (html snippet'ini oluşturmak için), etkin değer değişebilir. Eğer Stringnihai değildi, daha sonra TOCTTOU riskler yaygın olurdu.

Bu nedenle String, son değilse, arayana güvenemiyorsanız güvenli kod yazmak zorlaşır. Tabii ki, bu tam olarak Java kütüphanelerinin bulunduğu konumdur: güvenilmeyen uygulamalar tarafından çağrılabilirler, bu yüzden arayanlarına güvenmeye cesaret edemezler. Bu, Stringson olmasa güvenli kütüphane kodu yazmanın mantıksız derecede zor olacağı anlamına gelir .


Tabii ki, TOCTTOU güvenlik açığını char[], dizenin içini değiştirmek için yansıma kullanarak çıkarmak hala mümkündür , ancak zamanlamada biraz şans gerektirir.
Peter Taylor

2
@Peter Taylor, bundan daha karmaşık. Özel değişkenlere erişmek için yansımayı yalnızca SecurityManager size izin verirse kullanabilirsiniz. Küçük uygulamalara ve diğer güvenilmeyen kodlara bu izin verilmez ve bu nedenle char[]dize içindeki özel öğeyi değiştirmek için yansıma kullanamazlar ; bu nedenle, TOCTTOU güvenlik açığından yararlanamazlar.
DW

Biliyorum, ama bu hala uygulamaları ve imzalı uygulamaları bırakıyor - ve kendinden imzalı bir uygulama için uyarı iletişim kutusundan kaç kişi korkuyor?
Peter Taylor

2
@Peter, mesele bu değil. Mesele şu ki, güvenilir Java kütüphaneleri yazmak çok daha zor olacak ve Java kütüphanelerinde Stringnihai olmasa da bildirilen çok daha fazla güvenlik açığı olacaktı . Bu tehdit modeli bir süre ilgili olduğu sürece, savunmak önemli hale gelir. Küçük uygulamalar ve diğer güvenilir olmayan kodlar için önemli olduğu sürece, Stringgüvenilir uygulamalar için gerekli olmasa bile final yapmayı haklı çıkarmak yeterlidir . (Her durumda, güvenilir uygulamalar nihai olup olmadığına bakılmaksızın tüm güvenlik önlemlerini atlayabilir.)
DW

"Saldırı: kötü niyetli bir kişi, EvilString dizesinin bir alt sınıfını oluşturabilir, burada EvilString.startsWith () her zaman true değerini döndürür ..." - startsWith () sonsa değil.
Erik

4

Değilse, çok iş parçacıklı java uygulamaları bir karmaşa olurdu (gerçekte olduğundan daha kötü).

IMHO Son dizgilerin (değişmez) temel avantajı, doğal olarak iş parçacığı açısından güvenli olmalarıdır: senkronizasyon gerektirmezler (bu kodu yazmak bazen oldukça önemsizdir, ancak bundan daha azdır). Değişebilir olsaydı, çok iş parçacıklı pencere araç takımları gibi şeylerin çok zor olacağını garanti edin ve bunlar kesinlikle gereklidir.


3
finalsınıfların sınıfın değişebilirliği ile ilgisi yoktur.

Bu cevap soruyu ele almıyor. -1
FUZxxl

3
Jarrod Roberson: Eğer sınıf final olmasaydı, miras kullanarak değişebilir Dizeler yaratabilirsiniz.
ysdx

@JarrodRoberson sonunda biri hatayı fark eder. Kabul edilen cevap aynı zamanda nihai eşittir değişmezdir.
Tulains Córdova

1

StringNihai olmanın bir diğer avantajı , tıpkı değişmezlik gibi öngörülebilir sonuçlar sağlamasıdır. Stringancak bir sınıf, bir değer türü gibi birkaç senaryoda ele alınmalıdır (derleyici bile bunu destekler String blah = "test"). Bu nedenle, belirli bir değere sahip bir dize yaparsanız, tamsayılarla elde edeceğiniz beklentilere benzer şekilde, herhangi bir dizeye miras alınan belirli bir davranışa sahip olmasını beklersiniz.

Bu düşünün: Ya sen alt sınıf java.lang.Stringve aşırı Rode equalsveya hashCodeyöntemler? Aniden iki String “testi” artık birbirine eşit olamazdı.

Bunu yapabilseydim kaosu hayal et:

public class Password extends String {
    public Password(String text) {
        super(text);
    }

    public boolean equals(Object o) {
        return true;
    }
}

başka bir sınıf:

public boolean isRightPassword(String suppliedString) {
    return suppliedString != null && suppliedString.equals("secret");
}

public void login(String username, String password) {
    return isRightUsername(username) && isRightPassword(password);
}

public void loginTest() {
    boolean success = login("testuser", new Password("wrongpassword"));
    // success is true, despite the password being wrong
}

1

Arka fon

Finalin Java'da gösterilebileceği üç yer vardır:

Bir dersi final yapmak, sınıfın tüm alt sınıflarını engeller. Bir yöntemin sonlandırılması, yöntemin alt sınıflarının onu geçersiz kılmasını önler. Bir alanı final yapmak daha sonra değiştirilmesini önler.

yanılgılar

Nihai yöntemler ve alanlar etrafında gerçekleşen optimizasyonlar vardır.

Son bir yöntem, HotSpot'un satır içi ile optimize etmesini kolaylaştırır. Bununla birlikte, HotSpot bunu, aksi ispatlanana kadar geçersiz kılınmadığı varsayımı üzerinde çalıştığı için yöntem kesin olmasa bile yapar. SO hakkında bu konuda daha fazla bilgi

Son bir değişken agresif bir şekilde optimize edilebilir ve bununla ilgili daha fazla bilgi JLS bölümünde 17.5.3 okunabilir .

Ancak, bu anlayışla , bu optimizasyonların hiçbirinin bir sınıfı final yapmakla ilgili olmadığının farkında olunmalıdır . Bir sınıfı final yaparak performans kazancı yoktur.

Bir sınıfın son yönünün değişmezlikle hiçbir ilgisi yoktur. Biri nihai olmayan değişmez bir sınıfa ( BigInteger gibi ) veya değişebilir ve nihai bir sınıfa ( StringBuilder gibi ) sahip olabilir. Bir sınıf ise yaklaşık karar olmalıdır nihai olması tasarımın bir sorudur.

Son tasarım

Dizeler en çok kullanılan veri türlerinden biridir. Haritaların anahtarları olarak bulunurlar, kullanıcı adlarını ve şifreleri saklarlar, klavyeden veya web sayfasındaki bir alandan okuduğunuz şeydir. Dizeler her yerde .

Haritalar

String'i alt sınıflara ayırabiliyorsanız ne olacağını düşünmeniz gereken ilk şey, birisinin başka bir şekilde bir String gibi görünen mutable bir String sınıfı oluşturabileceğini fark etmektir. Bu, Haritalar'ı her yerde karıştırır.

Bu varsayımsal kodu düşünün:

Map t = new TreeMap<String, Integer>();
Map h = new HashMap<String, Integer>();
MyString one = new MyString("one");
MyString two = new MyString("two");

t.put(one, 1); h.put(one, 1);
t.put(two, 2); h.put(two, 2);

one.prepend("z");

Bu, genel olarak bir Harita ile değiştirilebilir bir anahtar kullanmakla ilgili bir sorun, ancak oraya ulaşmaya çalıştığım şey, aniden Harita kırılmasıyla ilgili bir dizi şey. Giriş artık haritada doğru noktada değil. Bir HashMap'te, karma değeri değişti (olması gerekir) ve bu nedenle artık doğru girişte değil. TreeMap'te, düğümlerden biri yanlış tarafta olduğundan ağaç şimdi kesildi.

Bu anahtarlar için bir Dize kullanımı çok yaygın olduğundan, Dize'nin sonlandırılmasıyla bu davranış önlenmelidir.

Eğer String neden Java ile değişmez?Dizelerin değişmez doğası hakkında daha fazla bilgi için.

Hain teller

Dizeler için çeşitli hain seçenekler vardır. Eşit çağrıldığında her zaman true döndüren bir String yaptım ve bir şifre kontrolüne geçip geçmediğimi düşünün. Veya MyString'e yapılan atamaların Dize'nin bir kopyasını bazı e-posta adreslerine göndermesini sağladı mı?

String'i alt sınıflandırma yeteneğiniz olduğunda bunlar çok gerçek olasılıklardır.

Java.lang Dize optimizasyonları

Daha önce bahsetmişken bu final String'i daha hızlı yapmaz. Bununla birlikte, String sınıfı (ve içindeki diğer sınıflar java.lang), diğer java.langsınıfların String için genel API'yi kullanmak yerine dahili sınıflarla uğraşmasını sağlamak için alanların ve yöntemlerin paket düzeyinde korumasını sık sık kullanır . Menzil denetimi olmayan getChars veya StringBuffer veya yapıcı tarafından kullanılan lastIndexOf gibi işlevler temel diziyi paylaşan (bellek sorunları nedeniyle değiştirilen bir Java 6 şeyi olduğunu unutmayın).

Birisi String'in bir alt sınıfını yaptıysa, bu optimizasyonları paylaşamazdı (eğer bir parçası olmadıkça java.lang, ama bu kapalı bir paket) ).

Uzatma için bir şeyler tasarlamak daha zor

Genişletilebilir bir şey tasarlamak zor . Bu, başka bir şeyin değiştirilebilmesi için iç kısımlarınızın bölümlerini açığa çıkarmanız gerektiği anlamına gelir.

Genişletilebilir bir Dizede bellek sızıntısı düzeltilemedi. Bu kısımlarının alt sınıflara maruz kalması ve bu kodun değiştirilmesi, alt sınıfların kırılacağı anlamına gelecektir.

Java, geriye dönük uyumlulukla gurur duyuyor ve çekirdek sınıfları uzantıya açarak, üçüncü taraf alt sınıflarıyla hesaplanabilirliği korurken, bazı şeyleri düzeltme yeteneğini kaybediyor.

Checkstyle , "DesignForExtension" adı verilen ve her sınıfın şunlardan birini uygulayan bir kurala sahiptir (iç kodu yazarken beni gerçekten sinirlendirir):

  • Öz
  • final
  • Boş uygulama

Rasyonel olan:

Bu API tasarım stili, üst sınıfları alt sınıfların kırılmasına karşı korur. Dezavantajı, alt sınıfların esnekliklerinde sınırlı olmalarıdır, özellikle de kodun üst sınıfta yürütülmesini engelleyemezler, ancak bu aynı zamanda alt sınıfların süper yöntemi çağırmayı unutarak üst sınıfın durumunu bozamayacağı anlamına gelir.

Uygulama sınıflarının genişletilmesine izin vermek, alt sınıfların dayandığı sınıfın durumunu bozabileceği ve üst sınıfın verdiği çeşitli garantilerin geçersiz olacağı anlamına gelebilir. String olarak kompleks olarak bir şey için, bu bir kesinlik neredeyse olduğunu bunun değişen parçası olacak şeyler bölünürler.

Geliştirici hurbis

Bir geliştirici olmanın bir parçası. Her geliştiricinin içinde kendi araçları koleksiyonu ile kendi String alt sınıflarını oluşturma olasılığını göz önünde bulundurun . Fakat şimdi bu alt sınıflar birbirlerine serbestçe atanamazlar.

WleaoString foo = new WleaoString("foo");
MichaelTString bar = foo; // This doesn't work.

Bu şekilde deliliğe yol açar. Her yerde String'e yayınlama ve String'in sizin için bir örnek olup olmadığını kontrol etme hayır, sadece ... onunla dayalı yeni dize yapma ve, String sınıfı ya da değil. Yapma.

Bence iyi Dize sınıf yazmak ... ama C ++ yazmak bu çılgın insanlara birden dize uygulamaları yazma ayrılıp uğraşmak zorunda eminim std::stringve char*artırmak ve gelen ve bir şey SString ve geri kalan tüm .

Java String sihri

Java'nın Dizelerle yaptığı birkaç sihirli şey var. Bunlar, bir programcının başa çıkmasını kolaylaştırır, ancak dilde bazı tutarsızlıklar ortaya çıkarır. String'de alt sınıflara izin vermek, bu büyülü şeylerle nasıl başa çıkılacağı hakkında çok önemli düşünceler alacaktı:

  • Dize Değişmezleri (JLS 3.10.5 )

    Birinin yapmasına izin veren kod olması:

    String foo = "foo";

    Bu, Tamsayı gibi sayısal türlerin boksuyla karıştırılmamalıdır. Yapamazsın 1.toString()ama yapabilirsin "foo".concat(bar).

  • +Operatör (JLS 15.18.1 )

    Java'da başka hiçbir başvuru türü üzerinde bir işleç kullanılmasına izin vermez. Dize özeldir. Dize birleştirme operatörü de derleyici düzeyinde çalışır, böylece çalışma zamanında değil derlendiğinde "foo" + "bar"olur "foobar".

  • Dize Dönüştürme (JLS 5.1.11 )

    Tüm nesneler yalnızca bir String bağlamında kullanılarak Dizelere dönüştürülebilir.

  • Dize Stajyerliği ( JavaDoc )

    String sınıfı, String değişmezleriyle derleme türünde doldurulan nesnenin kurallı temsillerine sahip olmasına izin veren Dizeler havuzuna erişebilir.

String'in bir alt sınıfına izin vermek, String'i programlamayı kolaylaştıran bu bitlerin, diğer String türleri mümkün olduğunda yapılması çok zor veya imkansız hale geleceği anlamına gelir.


0

Dize son olmasaydı, her programcı araç sandığı kendi "Sadece birkaç güzel yardımcı yöntemle dize" içerecektir. Bunların her biri diğer tüm alet sandıklarıyla uyumsuz olacaktır.

Aptalca bir kısıtlama olduğunu düşündüm. Bugün bunun çok sağlam bir karar olduğuna ikna oldum. Dizeleri Dizeler olarak tutar.


finalJava finalC # ile aynı şey değildir . Bu bağlamda, C # 'lerle aynı şeydir readonly. Bkz. Stackoverflow.com/questions/1327544/…
Arseni Mourzenko

9
@MainMa: Aslında, bu bağlamda C # 'in mühürlü olduğu gibi aynı görünüyor public final class String.
yapılandırıcı
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.