String sınıfı Java'da neden son olarak bildirildi?


141

Sınıfın java.lang.StringJava'da final olarak ilan edildiğini öğrendiğimden, bunun neden olduğunu merak ediyordum. O zamanlar herhangi bir cevap bulamadım, ancak bu yazı: Java'da String sınıfının bir kopyasını nasıl oluşturabilirim? sorgumu hatırlattı bana.

Tabii, String, şimdiye kadar ihtiyaç duyduğum tüm işlevselliği sağlar ve sınıf String'in bir uzantısını gerektiren herhangi bir işlem düşünmedim, ama yine de birinin neye ihtiyacı olabileceğini asla bilemezsiniz!

Peki, tasarımcıları nihai yapmaya karar verdiklerinde niyetinin ne olduğunu bilen var mı?


Cevaplarınız için hepinize teşekkür ederim, özellikle TrueWill, Bruno Reis ve Thilo! Keşke en iyi cevap olarak birden fazla cevap seçebilseydim ama maalesef ...!
Alex Ntousias

1
Ayrıca, "Oh, sadece farklı bir sınıf oldukları için birbirlerinin Dizeleri kullanamazlar - ortaya çıkacak Dize üzerinde birkaç daha kullanışlı yöntemlere ihtiyacım var" projelerinin çoğalmasını düşünün.
Thorbjørn Ravn Andersen

Bu cevap için teşekkürler çok yararlı. şimdi iki olgumuz var. Bir String bir Final sınıfıdır ve değiştirilemez çünkü değiştirilemez, ancak başka bir nesneye yönlendirilebilir. ama ne hakkında: - String a = new String ("test1"); sonra, s = "test2"; Dize Final sınıfı nesnesiyse, nasıl değiştirilebilir? Değiştirilmiş son nesneyi nasıl kullanabilirim? Yanlış bir şey sorduğumda lütfen izin ver.
Suresh Sharma


4
Java'da minnetle kaçındığımız bir şey, "herkesin birçok ekstra yöntemle String'in kendi alt sınıfına sahip olması ve bunların hiçbirinin birbiriyle uyumlu olmamasıdır.
Thorbjørn Ravn Andersen

Yanıtlar:


88

Değişmez nesneler olarak dizelerin uygulanması çok yararlıdır . Bu konuda daha fazla şey anlamak için değişmezliği okumalısınız .

Bir avantajı sabit nesneler olduğunu

Çoğaltmaları tek bir örneğe işaret ederek paylaşabilirsiniz.

( buradan ).

Dize son olmasaydı, bir alt sınıf oluşturabilir ve "Dizeler olarak görüldüğünde" birbirine benzeyen ancak aslında farklı olan iki dizeye sahip olabilirsiniz.


70
Final sınıfları ve göremediğim değişmez nesneler arasında bir bağlantı olmadığı sürece cevabınızın soru ile nasıl ilişkili olduğunu görmüyorum.
sepp2k

9
Çünkü bu son değilse bazı yöntemlere String parametresi olarak bir StringChild iletebilirsiniz ve değiştirilebilir olabilir (çünkü bir alt sınıf durum değişikliği).
helios

4
Vaov! Downvotes? Alt sınıflamanın değişmezlik ile nasıl bir ilişkisi olduğunu anlamıyor musunuz? Sorunun ne olduğu hakkında bir açıklama için teşekkür ederim.
Bruno Reis

7
@Bruno, re: downvotes: Seni küçümsemedim, ancak alt sınıfları önlemenin değişmezliği nasıl zorladığı konusunda bir cümle ekleyebilirsiniz. Şu anda, bu yarım bir cevap.
Thilo

12
@BrunoReis - Bunu link verebilecek güzel makale James Gosling ile röportaj (Java yaratıcısı) sahiptir bulduğu yer bu konu hakkında o kısaca görüşmeler burada . İlginç bir snippet: "Dizeleri değişmez olmaya zorlayan şeylerden biri güvenlikti. Bir dosya açma yönteminiz var. Bir String geçirin. Ve sonra işletim sistemini yapmaya başlamadan önce her türlü kimlik doğrulama kontrolünü yapıyor Güvenlik kontrolünden sonra ve işletim sistemi çağrısından önce, String'i etkili bir şekilde mutasyona uğratan bir şey yapmayı başarırsanız, o zaman
patlarsınız

60

Bu, yukarıdaki cevaplarda daha önce bahsedilen iki nedeni özetleyen güzel bir makaledir :

  1. Güvenlik : sistem, değiştirileceklerinden endişe etmeden, salt okunur bilgilerin hassas parçalarını dağıtabilir
  2. Performans : değişmez veriler, işleri iş parçacığı açısından güvenli hale getirmek için çok yararlıdır.

Ve bu muhtemelen bu makaledeki en ayrıntılı yorumdur. Java ve güvenlik sorunları dize havuzu ile ilgisi vardır. Onun dize havuza ne karar vermek hakkında. Karakter dizileri aynı ise her iki dizenin de eşit olduğunu varsayarsak, oraya ilk kimin ve güvenlik sorunlarıyla birlikte geldiği konusunda bir yarış koşulumuz vardır. Değilse, dize havuzu yedek dizeler içerecek ve böylece ilk etapta sahip olma avantajını kaybedecektir. Sadece kendiniz okuyun, değil mi?


Dize genişletmek eşittir ve stajyer ile tahrip eder. JavaDoc diyor ki:

Bu dizeyi belirtilen nesneyle karşılaştırır. Sonuç, yalnızca bağımsız değişken null değilse ve bu nesneyle aynı karakter sırasını temsil eden bir String nesnesiyse doğrudur.

Varsayarak java.lang.Stringbir nihai değildi SafeStringbir eşit olabilir String, veya tam tersi; çünkü aynı karakter dizisini temsil ediyorlardı.

Uyguladığınız ne olur interna SafeString- olur SafeStringJVM dize havuza gitmek? ClassLoaderVe tüm nesneleri SafeStringsonra JVM ömrü boyunca yerinde kilitli alacağı için bekletilen başvurular. Bir dizi karakteri ilk kimin araya sokacağı konusunda bir yarış koşulu elde edersiniz - belki SafeStringde kazanırsınız, belki a String, ya da belki SafeStringfarklı bir sınıf yükleyici (böylece farklı bir sınıf) tarafından yüklenir.

Eğer havuza yarış kazanırsanız, bu gerçek bir singleton olacaktır ve insanlar tüm ortamınıza (sandbox) yansıma yoluyla erişebilir ve secretKey.intern().getClass().getClassLoader().

Veya JVM, havuza yalnızca somut String nesnelerinin (ve alt sınıfların) eklenmediğinden emin olarak bu deliği engelleyebilir.

Eğer eşitler uygulanmışsa SafeString! = StringO zaman SafeString.intern! = String.internVe SafeStringhavuza eklenmesi gerekir. Havuz daha sonra <Class, String>bunun yerine bir havuz haline gelecek ve havuza <String>girmek için ihtiyacınız olan her şey yeni bir sınıf yükleyici olacaktır.


2
Tabii ki performans nedeni bir yanılgıdır: String bir arayüz olsaydı, benim uygulamada daha iyi performans gösteren bir uygulama sağlamak mümkün olurdu.
Stephan Eggermont

28

String'in değişmez veya nihai olmasının en önemli nedeni, sınıf yükleme mekanizması tarafından kullanılması ve dolayısıyla derin ve temel güvenlik yönlerine sahip olmasıdır.

Dize değiştirilebilir veya son olmasaydı, "java.io.Writer" yükleme isteği "mil.vogoon.DiskErasingWriter" yüklemek için değiştirilmiş olabilir

başvuru: String neden Java ile değiştirilemez


15

String Java'da çok temel bir sınıftır, pek çok şey, örneğin değişmez olmak gibi belirli bir şekilde çalışmasına güvenir.

Sınıf finaloluşturmak, bu varsayımları kırabilecek alt sınıfları önler.

Şimdi bile, yansıma kullanıyorsanız, Dizeleri kırabileceğinizi (değerlerini veya karma kodunu değiştirebileceğinizi) unutmayın. Yansıma bir güvenlik yöneticisi ile durdurulabilir. Eğer Stringdeğildi final, herkes bunu yapabilir.

Bildirilmeyen diğer sınıflar, bir finalşekilde kırık alt sınıfları tanımlamanıza izin verir ( Listörneğin, yanlış konuma eklenmiş bir sınıfınız olabilir ), ancak en azından JVM, temel işlemleri için olanlara bağlı değildir.


6
finalbir sınıfta değişmezliği garanti etmez. Sadece bir sınıfın değişmezlerinin (biri değişmez olabilir) bir alt sınıf tarafından değiştirilemeyeceğini garanti eder.
Kevin Brock

1
@Kevin: evet. Bir sınıf finali, alt sınıf olmadığını garanti eder. Değişmezlik ile ilgisi yoktur.
Thilo

4
Bir sınıfı final yapmak tek başına onu kaçınılmaz kılmaz. Ancak değişmez bir sınıfın nihai yapılması, kimsenin değişmezliği kıran bir alt sınıf yapmamasını sağlar. Belki de değişmezliğe dikkat çeken insanlar tam olarak ne demek istedikleri konusunda net değillerdi, ancak ifadeleri bağlamda anlaşıldığında doğrudur.
Jay

Bazı zamanlar bu cevabı okudum ve bunun iyi bir cevap olduğunu düşündüm, sonra 'Etkili Java' dan hashcode & eşittir okudum ve bunun çok iyi bir cevap olduğunu fark ettim. Herkesin açıklamalara ihtiyacı var, aynı kitabın ieam 8 ve iteam 9'unu öneriyorum.
Abhishek Singh

6

Bruno'nun dediği gibi değişmezlikle ilgili. Sadece Dizeler ile ilgili değil, örneğin Double, Integer, Character, vb.

  • İplik güvenliği
  • Güvenlik
  • Java tarafından yönetilen yığın (farklı şekilde Çöp Toplanan sıradan yığıntan farklı olarak)
  • Bellek yönetimi

Temel olarak, bir programcı olarak, dizenizin asla değişmeyeceğinden emin olabilirsiniz. Nasıl çalıştığını biliyorsanız, bellek yönetimini de geliştirebilir. Birbiri ardına iki özdeş dize, örneğin "merhaba" oluşturmaya çalışın. Hata ayıklarsanız, aynı kimliklere sahip olduklarını fark edeceksiniz, bu tam olarak aynı nesneler oldukları anlamına gelir. Bunun nedeni Java'nın bunu yapmasına izin vermesidir. Dizeler sessiz olsaydı bu mümkün olmazdı. Aynı şeylere sahip olabilirler, çünkü asla değişmeyecekler. Dolayısıyla, 1.000.000 dize "merhaba" oluşturmaya karar verirseniz, gerçekten yapacağınız "merhaba" için 1.000.000 işaretçi oluşturmaktır. Dize üzerindeki herhangi bir işlevi veya bu nedenle herhangi bir sarmalayıcıyı da barındırmak, başka bir nesne oluşturmaya neden olur (tekrar nesne kimliğine bakın - değişecektir).

Aditionally Java nihai değil mutlaka bu nesne değiştiremez demek (bu ++ örneğin C için farklıdır). Bu, işaret ettiği adresin değiştirilemeyeceği, ancak yine de özelliklerini ve / veya niteliklerini değiştirebileceğiniz anlamına gelir. Bu nedenle, bazı durumlarda değişmezlik ve nihai arasındaki farkı anlamak gerçekten önemli olabilir.

HTH

Referanslar:


1
Ben dizeleri farklı bir yığın gidin veya farklı bir bellek yönetimi kullanmak inanmıyorum. Kesinlikle çöp toplanabilirler.
Thilo

2
Ayrıca, bir sınıftaki son anahtar kelime bir alandaki son anahtar kelimeden tamamen farklıdır.
Thilo

1
Tamam, Sun'ın JVM'sinde, stajyer () dizeleri, yığının bir parçası olmayan perma genine girebilir. Ancak bu, tüm Dizeler veya JVM için kesinlikle gerçekleşmez.
Thilo

2
Tüm Dizeler o alana gitmez, sadece staj yapılan Dizeler. Staj, gerçek Dizeler için otomatiktir. (@Thilo, yorumunuzu gönderdiğiniz gibi yazarak).
Kevin Brock

Bu cevap için teşekkürler çok yararlı. şimdi iki olgumuz var. Bir String bir Final sınıfıdır ve değiştirilemez çünkü değiştirilemez, ancak başka bir nesneye yönlendirilebilir. ama ne hakkında: - String a = new String ("test1"); sonra, s = "test2"; Dize Final sınıfı nesnesiyse, nasıl değiştirilebilir? Değiştirilmiş son nesneyi nasıl kullanabilirim? Yanlış bir şey sorduğumda lütfen izin ver.
Suresh Sharma

2

Uygulamayı basitleştirmek olabilir. Sınıf kullanıcıları tarafından devralınacak bir sınıf tasarlarsanız, tasarımınızı dikkate almanız için yepyeni bir dizi kullanım durumunuz olur. Bunu veya X korumalı alanı ile yaparlarsa ne olur? Son olarak, genel arayüzün düzgün çalışmasına ve sağlam olduğundan emin olmaya odaklanabilirler.


3
+1 "kalıtım için tasarım zor". BTW, bu Bloch'un "Etkili Java" sında çok güzel açıklandı.
sleske

2

Zaten iyi bir çok noktaya değinmek istiyorum, bir başka bir tane daha eklemek istiyorum -Dize Java'da değişmez olmasının nedenlerinden biri, String'in hashcode'unu önbelleğe almasına izin vermek, Java'daki değişmez String hash kodunu önbelleğe alıyor ve her hesaplamıyor dize hashcode yöntemini çağırdığımız zaman , Java'da hashmap'de kullanılacak hashmap anahtarı olarak çok hızlı hale getirir.

Kısacası String değişmez olduğu için, hiç kimse oluşturulduktan sonra içeriğini değiştiremez, bu da String'in hashCode'unun çoklu çağırmada aynı olmasını sağlar.

Görürseniz Stringsınıf olarak ilan edilir etti

/** Cache the hash code for the string */
private int hash; // Default to 0

ve hashcode()fonksiyon aşağıdaki gibidir -

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

Zaten bilgisayar ise, sadece değeri döndürün.


2

Daha iyi bir uygulama elde etmediğimizden emin olmak için. Tabii ki bir arayüz olmalı.

Ah, daha aşağı oylar clueless. Cevap son derece ciddidir. Birkaç kez aptal String uygulaması etrafında programlamak zorunda kaldım, bu da ciddi performans ve verimlilik kaybına yol açtı


2

Diğer cevaplarda önerilen bariz nedenlerin yanı sıra, String sınıfını final yapmak gibi bir düşünce de sanal yöntemler performans yükü ile ilgili olabilir. Hatırlayın String ağır bir sınıftır, bu finali yapar, kesin bir alt uygulama anlamına gelmez, hiçbir dolaylı yükü çağırmaz. Elbette şimdi sanal invoke ve diğerleri gibi her zaman sizin için bu tür bir optimizasyon yapan şeyler var.


2

Diğer cevaplarda belirtilen nedenlere ek olarak (güvenlik, değişmezlik, performans) Stringözel dil desteğine sahip olduğuna dikkat edilmelidir . StringDeğişmez değerler yazabilirsiniz ve +operatör için destek vardır . Programcıların alt sınıfa girmesine izin vermek String, aşağıdaki gibi saldırıları teşvik eder:

class MyComplex extends String { ... }

MyComplex a = new MyComplex("5+3i");
MyComplex b = new MyComplex("7+4i");
MyComplex c = new MyComplex(a + b);   // would work since a and b are strings,
                                      // and a string + a string is a string.

1

Eh, ben doğru olup olmadığını emin değilim bazı farklı düşünce var ama Java Dize de ilkel bir veri türü olarak tedavi edilebilir tek nesne yanı sıra biz bir String nesnesi oluşturabilirsiniz Dize name = "java " . Şimdi olan ilkel veri türleri diğerleri gibi değeriyle kopya değil referans yoluyla kopya dize dize kesindir neden öylesine thats aynı davranışa sahip olması bekleniyor. Benim düşüncem bu. Lütfen tamamen mantıksızsa görmezden gelin.


1
Bence String asla ilkel bir tip gibi davranmaz. "Java" gibi bir dize hazır bilgisi aslında String sınıfının bir nesnesidir (kapama teklifinden hemen sonra üzerinde nokta işleci kullanabilirsiniz). Bu nedenle, bir dize değişkenine değişmez değer atamak, her zamanki gibi nesne başvurularını atamaktır. Farklı olan, String sınıfının derleyiciye yerleşik dil düzeyinde desteğe sahip olmasıdır ... çift tırnak içindeki şeyleri String nesnelerine ve yukarıda belirtildiği gibi + işlecine dönüştürür.
Georgie

1

Dizelerin kesinliği de onları standart olarak savunur. C ++ 'da dize alt sınıfları oluşturabilirsiniz, böylece her programlama mağazası kendi dize sürümüne sahip olabilir. Bu, güçlü bir standart eksikliğine yol açacaktır.


1

Diyelim ki Employeeyöntemi olan bir sınıfınız var greet. Ne zaman greetyöntem olarak adlandırılır basitçe yazdırır Hello everyone!. Bu , yöntemin beklenen davranışıdır .greet

public class Employee {

    void greet() {
        System.out.println("Hello everyone!");
    }
}

Şimdi, aşağıda gösterildiği gibi GrumpyEmployeealt sınıfı Employeeve geçersiz kılma greetyöntemini bırakın .

public class GrumpyEmployee extends Employee {

    @Override
    void greet() {
        System.out.println("Get lost!");
    }
}

Şimdi aşağıdaki kodu sayHelloyöntemi bir göz atın . Tek gereken Employeebir parametre olarak örneği ve bunun derdi umuduyla Greet yöntemini çağıran Hello everyone!Ama biz olsun Get lost!. Bu davranış değişikliği nedeniyleEmployee grumpyEmployee = new GrumpyEmployee();

public class TestFinal {
    static Employee grumpyEmployee = new GrumpyEmployee();

    public static void main(String[] args) {
        TestFinal testFinal = new TestFinal();
        testFinal.sayHello(grumpyEmployee);
    }

    private void sayHello(Employee employee) {
        employee.greet(); //Here you would expect a warm greeting, but what you get is "Get lost!"
    }
}

Bu durum olabilir kaçınılması durumunda Employeesınıf yapıldı final. StringSınıf olarak ilan edilmezse, arsız bir programcının neden olabileceği kaos miktarı hayal gücünüze bağlıdır final.


0

JVM değişmez olanı biliyor mu? Yanıt Hayır, Sabit havuz tüm değiştirilemez alanları içerir, ancak tüm değiştirilemez alanlar / nesneler yalnızca sabit havuzda depolanmaz. Sadece bunu, değişmezliği ve özelliklerini elde edecek şekilde uygularız. CustomString, havuzlaması için java özel davranışı sağlayacak MarkerInterface kullanılarak sonlandırılmadan uygulanabilir, özellik hala beklenmektedir!


0

Cevapların çoğu değişmezlikle ilgilidir - String türündeki bir nesnenin neden yerinde güncellenemediği. Burada çok iyi bir tartışma var ve Java topluluğu değişmezliği bir prensip olarak benimsemek için iyi bir şey yapardı. (Nefesimi tutmuyorum.)

Ancak OP'nin sorusu neden nihai olduğu ile ilgilidir - neden genişletilemiyor? Burada bazıları bunu üstlendi, ancak OP ile burada gerçek bir boşluk olduğu konusunda hemfikirim. Diğer diller, geliştiricilerin bir tür için yeni nominal türler oluşturmasına izin verir. Örneğin Haskell'de çalışma zamanında metin olarak aynı olan ancak derleme zamanında bağlama güvenliği sağlayan aşağıdaki yeni türleri oluşturabilirim.

newtype AccountCode = AccountCode Text
newtype FundCode = FundCode Text

Bu nedenle, Java dilinde bir geliştirme olarak aşağıdaki öneriyi ileri sürdüm:

newtype AccountCode of String;
newtype FundCode of String;

AccountCode acctCode = "099876";
FundCode fundCode = "099876";

acctCode.equals(fundCode);  // evaluates to false;
acctCode.toString().equals(fundCode.toString());  // evaluates to true;

acctCode=fundCode;  // compile error
getAccount(fundCode);  // compile error

(Ya da kendimizi Java'dan ayırmaya başlayabiliriz)


-1

Bir kez bir dize oluşturursanız Bunu dikkate alacaktır, bunu değiştirmek istiyorsanız bir nesne, mümkün değil, yeni nesne oluşturacaktır.


Lütfen cevabınızı netleştirin.
Marko Popovic
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.