Mengapa kelas String dinyatakan final di Jawa?

141

Dari ketika saya mengetahui bahwa kelas java.lang.Stringdinyatakan sebagai final di Jawa, saya bertanya-tanya mengapa itu. Saya tidak menemukan jawaban saat itu, tetapi posting ini: Bagaimana cara membuat replika kelas String di Jawa? mengingatkan saya pada pertanyaan saya.

Tentu, String menyediakan semua fungsionalitas yang pernah saya butuhkan, dan saya tidak pernah memikirkan operasi apa pun yang membutuhkan ekstensi kelas String, tetapi Anda tetap tidak akan pernah tahu apa yang mungkin dibutuhkan seseorang!

Jadi, adakah yang tahu apa maksud para desainer itu ketika mereka memutuskan untuk menjadikannya final?

Alex Ntousias
sumber
Terima kasih atas jawaban Anda, terutama TrueWill, Bruno Reis dan Thilo! Saya berharap saya bisa memilih lebih dari satu jawaban sebagai yang terbaik, tetapi sayangnya ...!
Alex Ntousias
1
Juga pertimbangkan proliferasi proyek "Oh, aku hanya perlu beberapa metode utilitas pada String" yang akan muncul - yang semuanya tidak dapat menggunakan satu sama lain Strings karena mereka adalah kelas yang berbeda.
Thorbjørn Ravn Andersen
Terima kasih atas balasan ini, ini sangat berguna. kami punya dua fakta sekarang. String adalah kelas Final & tidak dapat diubah karena tidak dapat diubah tetapi dapat dirujuk ke objek lain. tetapi bagaimana dengan: - String a = new String ("test1"); lalu, s = "test2"; Jika String adalah objek kelas Final lalu bagaimana hal itu dapat dimodifikasi? Bagaimana saya bisa menggunakan objek akhir yang dimodifikasi. Tolong beritahu saya jika saya salah bertanya.
Suresh Sharma
Anda dapat melihat artikel yang bagus ini .
Aniket Thakur
4
Satu hal yang untungnya kami hindari di Jawa adalah "setiap orang memiliki subkelas String mereka sendiri dengan banyak metode tambahan, dan tidak ada yang kompatibel satu sama lain".
Thorbjørn Ravn Andersen

Jawaban:

88

Sangat berguna untuk menerapkan string sebagai objek yang tidak berubah . Anda harus membaca tentang kekekalan untuk memahami lebih banyak tentang itu.

Satu keuntungan dari objek yang tidak berubah adalah itu

Anda dapat membagikan duplikat dengan mengarahkannya ke satu instance.

(dari sini ).

Jika String tidak final, Anda bisa membuat subclass dan memiliki dua string yang mirip ketika "dilihat sebagai String", tetapi itu sebenarnya berbeda.

Bruno Reis
sumber
70
Kecuali jika ada hubungan antara kelas akhir dan objek tidak berubah yang tidak saya lihat, saya tidak melihat bagaimana jawaban Anda terkait dengan pertanyaan.
sepp2k
9
Karena jika ini belum final, Anda dapat melewatkan StringChild ke beberapa metode sebagai parameter Param, dan itu bisa berubah-ubah (karena perubahan status kelas anak).
helios
4
Wow! Downvotes? Apakah Anda tidak mengerti bagaimana subclassing berhubungan dengan ketidakberdayaan? Saya menghargai penjelasan tentang apa masalahnya.
Bruno Reis
7
@ Bruno, re: downvotes: Saya tidak melakukan downvote pada Anda, tetapi Anda dapat menambahkan kalimat tentang bagaimana mencegah subclass memberlakukan immutability. Saat ini, itu semacam setengah jawaban.
Thilo
12
@BrunoReis - menemukan artikel bagus yang bisa Anda tautkan dengan wawancara dengan James Gosling (pembuat Java) di mana ia secara singkat berbicara tentang topik ini di sini . Berikut cuplikan yang menarik: "Salah satu hal yang memaksa String tidak dapat diubah adalah keamanan. Anda memiliki metode buka file. Anda memberikan String padanya. Dan kemudian melakukan semua jenis pemeriksaan otentikasi sebelum berkeliling untuk melakukan OS panggilan. Jika Anda berhasil melakukan sesuatu yang secara efektif mengubah String, setelah pemeriksaan keamanan dan sebelum panggilan OS, maka boom, Anda berada di ... "
Anurag
60

Ini adalah artikel yang bagus yang menguraikan dua alasan yang telah disebutkan pada jawaban di atas:

  1. Keamanan : sistem dapat membagikan bit sensitif informasi hanya baca tanpa khawatir mereka akan diubah
  2. Performa : data yang tidak dapat diubah sangat berguna dalam membuat hal-hal yang aman.

Dan ini mungkin komentar paling detail dalam artikel itu. Ini ada hubungannya dengan kumpulan string di Jawa dan masalah keamanan. Ini tentang bagaimana memutuskan apa yang masuk ke kolam string. Dengan asumsi kedua string sama jika urutan karakter mereka sama, maka kita memiliki kondisi balapan tentang siapa yang sampai di sana terlebih dahulu dan bersamaan dengan itu masalah keamanan. Jika tidak, maka kumpulan string akan berisi string yang berlebihan sehingga kehilangan keuntungan dari memilikinya di tempat pertama. Bacakan saja sendiri, ya?


Extending String akan memainkan malapetaka dengan equals dan intern. JavaDoc mengatakan equals:

Membandingkan string ini dengan objek yang ditentukan. Hasilnya benar jika dan hanya jika argumennya tidak nol dan merupakan objek String yang mewakili urutan karakter yang sama dengan objek ini.

Dengan asumsi java.lang.Stringitu belum final, a SafeStringbisa sama dengan String, dan sebaliknya; karena mereka mewakili urutan karakter yang sama.

Apa yang akan terjadi jika Anda melamar internke SafeString- apakah SafeStringmasuk ke kumpulan string JVM? The ClassLoaderdan semua objek yang SafeStringreferensi diadakan untuk kemudian akan mendapatkan terkunci di tempat untuk seumur hidup dari JVM. Anda akan mendapatkan kondisi balapan tentang siapa yang bisa menjadi orang pertama yang magang serangkaian karakter - mungkin Anda SafeStringakan menang, mungkin a String, atau mungkin SafeStringdimuat oleh classloader yang berbeda (dengan demikian kelas yang berbeda).

Jika Anda memenangkan perlombaan ke kolam renang, ini akan menjadi singleton sejati dan orang-orang dapat mengakses seluruh lingkungan Anda (kotak pasir) melalui refleksi dan secretKey.intern().getClass().getClassLoader().

Atau JVM dapat memblokir lubang ini dengan memastikan bahwa hanya objek String beton (dan tidak ada subkelas) yang ditambahkan ke kumpulan.

Jika persamaan diterapkan sedemikian rupa SafeString! = StringMaka SafeString.intern! = String.intern, Dan SafeStringharus ditambahkan ke kumpulan. Kolam kemudian akan menjadi kolam <Class, String>bukan <String>dan semua yang Anda butuhkan untuk memasuki kolam akan menjadi classloader segar.

Anurag
sumber
2
Tentu saja alasan kinerjanya salah: seandainya String menjadi antarmuka saya akan dapat memberikan implementasi yang berkinerja lebih baik dalam aplikasi saya.
Stephan Eggermont
28

Alasan yang paling penting bahwa String tidak dapat diubah atau final adalah bahwa String digunakan oleh mekanisme pemuatan kelas, dan dengan demikian memiliki aspek keamanan yang mendasar dan mendasar.

Seandainya String tidak bisa diubah atau belum final, permintaan untuk memuat "java.io.Writer" dapat diubah untuk memuat "mil.vogoon.DiskErasingWriter"

referensi: Mengapa String tidak dapat diubah di Jawa

Jackob
sumber
15

String adalah kelas yang sangat inti di Jawa, banyak hal bergantung padanya bekerja dengan cara tertentu, misalnya menjadi tidak berubah.

Membuat kelas finalmencegah subclass yang bisa mematahkan asumsi ini.

Perhatikan bahwa, bahkan sekarang, jika Anda menggunakan refleksi, Anda dapat memecah Strings (mengubah nilai atau kode hash). Refleksi dapat dihentikan dengan manajer keamanan. Jika Stringtidak final, semua orang bisa melakukannya.

Kelas-kelas lain yang tidak dideklarasikan finalmemungkinkan Anda untuk mendefinisikan subclass yang agak rusak (Anda bisa memiliki Listyang menambah posisi yang salah, misalnya) tetapi setidaknya JVM tidak bergantung pada mereka untuk operasi intinya.

Thilo
sumber
6
finaldi kelas tidak menjamin kekekalan. Ini hanya menjamin bahwa invarian kelas (salah satunya bisa berubah) tidak dapat diubah oleh sub-kelas.
Kevin Brock
1
@ Kevin: ya. Final on a class menjamin bahwa tidak ada subclass. Tidak ada hubungannya dengan kekekalan.
Thilo
4
Membuat final kelas tidak dengan sendirinya membuatnya tidak bisa diubah. Tetapi membuat final kelas abadi memastikan bahwa tidak ada yang membuat subclass yang merusak keabadian. Mungkin orang-orang yang mengutarakan soal kekekalan tidak jelas dalam arti sebenarnya, tetapi pernyataan mereka benar ketika dipahami dalam konteks.
Jay
Beberapa waktu yang lalu saya membaca jawaban ini dan saya pikir itu jawaban okey, kemudian saya membaca kode hash & sama dengan 'Java Efektif' dan menyadari itu jawaban yang sangat bagus. Siapa pun perlu penjelasan, saya sarankan ieam 8 & iteam 9 dari buku yang sama.
Abhishek Singh
6

Seperti yang dikatakan Bruno, ini tentang kekekalan. Ini bukan hanya tentang Strings tetapi juga tentang pembungkus misalnya Double, Integer, Karakter, dll. Ada banyak alasan untuk ini:

  • Keamanan benang
  • Keamanan
  • Heap yang dikelola oleh Jawa itu sendiri (berbeda dengan heap biasa yaitu Sampah Dikumpulkan dengan cara yang berbeda)
  • Manajemen memori

Pada dasarnya itu agar Anda, sebagai programmer, dapat memastikan bahwa string Anda tidak akan pernah berubah. Itu juga, jika Anda tahu cara kerjanya, dapat meningkatkan manajemen memori. Cobalah untuk membuat dua string yang identik satu demi satu, misalnya "halo". Anda akan melihat, jika Anda men-debug, bahwa mereka memiliki ID yang identik, itu berarti bahwa mereka persis objek SAMA. Ini disebabkan oleh fakta bahwa Java memungkinkan Anda melakukannya. Ini tidak akan mungkin jika string dapat dipecahkan. Mereka dapat memiliki aku yang sama, dll., Karena mereka tidak akan pernah berubah. Jadi jika Anda pernah memutuskan untuk membuat 1.000.000 string "halo" yang benar-benar Anda lakukan adalah membuat 1.000.000 petunjuk untuk "halo". Serta menyatukan fungsi apa pun pada string, atau pembungkus apa pun untuk alasan itu, akan menghasilkan pembuatan objek lain (sekali lagi lihat ID objek - itu akan berubah).

Final di Jawa tidak selalu berarti bahwa objek tidak dapat berubah (berbeda dengan misalnya C ++). Ini berarti bahwa alamat yang ditunjuknya tidak dapat diubah, tetapi Anda masih dapat mengubah properti dan / atau atributnya. Jadi memahami perbedaan antara kekekalan dan final dalam beberapa kasus mungkin sangat penting.

HTH

Referensi:

Artur
sumber
1
Saya tidak percaya bahwa Strings pergi ke tumpukan yang berbeda atau menggunakan manajemen memori yang berbeda. Mereka tentu saja sampah yang bisa dikumpulkan.
Thilo
2
Juga, kata kunci terakhir pada suatu kelas sama sekali berbeda dari kata kunci terakhir untuk suatu bidang.
Thilo
1
Oke, di Sun's JVM, Strings yang intern () bisa masuk ke perm-gen, yang bukan bagian dari heap. Tapi itu pasti tidak terjadi untuk semua Strings, atau semua JVM.
Thilo
2
Tidak semua String masuk ke area itu, hanya String yang sudah diinternir. Interning otomatis untuk Strings literal. (@Thilo, ketikan saat Anda mengirim komentar).
Kevin Brock
Terima kasih atas balasan ini, ini sangat berguna. kami punya dua fakta sekarang. String adalah kelas Final & tidak dapat diubah karena tidak dapat diubah tetapi dapat dirujuk ke objek lain. tetapi bagaimana dengan: - String a = new String ("test1"); lalu, s = "test2"; Jika String adalah objek kelas Final lalu bagaimana hal itu dapat dimodifikasi? Bagaimana saya bisa menggunakan objek akhir yang dimodifikasi. Tolong beritahu saya jika saya salah bertanya.
Suresh Sharma
2

Mungkin untuk menyederhanakan implementasi. Jika Anda mendesain kelas yang akan diwariskan oleh pengguna kelas, maka Anda memiliki satu set kasus penggunaan yang sama sekali baru untuk dipertimbangkan ke dalam desain Anda. Apa yang terjadi jika mereka melakukan ini atau itu dengan bidang proptected X? Menjadikannya final, mereka dapat fokus untuk membuat antarmuka publik berfungsi dengan benar dan memastikannya solid.

AaronLS
sumber
3
+1 untuk "mendesain pewarisan sulit". BTW, itu dijelaskan dengan sangat baik dalam "Java Efektif" milik Bloch.
sleske
2

Dengan banyak poin bagus yang telah disebutkan saya ingin menambahkan satu lagi -salah satu alasan Mengapa String tidak dapat diubah di Jawa adalah untuk memungkinkan String untuk me-cache hashcode-nya , menjadi String yang tidak dapat diubah di Jawa cache hashcode-nya, dan tidak menghitung setiap kali kita memanggil metode kode hash String , yang membuatnya sangat cepat sebagai kunci hashmap untuk digunakan dalam hashmap di Jawa.

Singkatnya karena String tidak dapat diubah, tidak ada yang dapat mengubah isinya yang pernah dibuat yang menjamin kode hash String sama pada beberapa pemanggilan.

Jika Anda melihat Stringkelas telah dinyatakan sebagai

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

dan hashcode()fungsinya adalah sebagai berikut -

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;
}

Jika sudah komputer cukup kembalikan nilainya.

Aniket Thakur
sumber
2

Untuk memastikan kami tidak mendapatkan implementasi yang lebih baik. Tentu saja itu seharusnya sebuah antarmuka.

[sunting] Ah, mendapatkan lebih banyak suara yang tidak mengerti. Jawabannya sangat serius. Saya harus memprogram jalan saya di sekitar implementasi String bodoh beberapa kali, yang mengarah ke kinerja yang parah & hilangnya produktivitas

Stephan Eggermont
sumber
2

Terlepas dari alasan yang jelas yang disarankan dalam jawaban lain, satu pemikiran untuk membuat kelas String akhir juga dapat dikaitkan dengan overhead kinerja metode virtual. Ingat String adalah kelas berat, membuat ini final, berarti tidak ada sub-implementasi pasti, berarti tidak ada tipuan yang memanggil overhead. Tentu saja sekarang kami memiliki hal-hal seperti pemanggilan virtual dan lainnya, yang selalu melakukan optimasi semacam ini untuk Anda.

Abhishek Singh
sumber
2

Selain alasan yang disebutkan dalam jawaban lain (keamanan, kekekalan, kinerja) harus dicatat bahwa Stringmemiliki dukungan bahasa khusus. Anda dapat menulis Stringliteral dan ada dukungan untuk +operator. Mengizinkan pemrogram untuk subkelas String, akan mendorong peretasan seperti:

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.
aioobe
sumber
1

Yah, saya punya beberapa pemikiran yang berbeda saya tidak yakin apakah saya benar atau tidak tetapi dalam Java String adalah satu-satunya objek yang dapat diperlakukan sebagai tipe data primitif juga maksud saya kita dapat membuat objek String sebagai String name = "java " . Sekarang seperti tipe data primitif lainnya yang disalin oleh nilai, bukan menyalin dengan referensi, String diharapkan memiliki perilaku yang sama, jadi itu sebabnya String bersifat final. Itulah yang saya pikirkan. Harap abaikan jika ini sama sekali tidak masuk akal.

webdev
sumber
1
Menurut pendapat saya, String tidak pernah berperilaku seperti tipe primitif. Sebuah string literal seperti "java" sebenarnya adalah objek dari kelas String (Anda dapat menggunakan operator titik di atasnya, segera setelah kutipan penutup). Jadi menugaskan literal ke variabel string hanya memberikan referensi objek seperti biasa. Apa yang berbeda adalah bahwa kelas String memiliki dukungan tingkat bahasa yang dibangun ke dalam kompiler ... mengubah hal-hal dalam tanda kutip ganda menjadi objek String, dan operator + seperti yang disebutkan di atas.
Georgie
1

Finalitas string juga membela mereka sebagai standar. Di C ++ Anda bisa membuat subclass dari string, sehingga setiap toko pemrograman bisa memiliki versi stringnya sendiri. Ini akan menyebabkan kurangnya standar yang kuat.

ncmathsadist
sumber
1

Katakanlah Anda memiliki Employeekelas yang memiliki metode greet. Ketika greetmetode ini disebut hanya mencetak Hello everyone!. Jadi itu adalah perilaku yang diharapkan dari greetmetode

public class Employee {

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

Sekarang, biarkan metode GrumpyEmployeesubclass Employeedan override greetseperti yang ditunjukkan di bawah ini.

public class GrumpyEmployee extends Employee {

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

Sekarang dalam kode di bawah ini lihat sayHellometode. Dibutuhkan Employeeinstance sebagai parameter dan memanggil metode sapaan berharap itu akan mengatakan Hello everyone!Tapi apa yang kita dapatkan adalah Get lost!. Perubahan perilaku ini karenaEmployee 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!"
    }
}

Situasi ini dapat dihindari jika Employeekelas dibuat final. Sekarang terserah pada imajinasi Anda jumlah kekacauan yang bisa disebabkan oleh programmer yang nakal jika StringKelas tidak dinyatakan sebagai final.

Andy
sumber
0

Apakah JVM tahu apa yang tidak bisa diubah? Jawabannya adalah Tidak, kolam konstan berisi semua bidang tidak berubah tetapi semua bidang tidak berubah hanya disimpan di kolam konstan saja. Hanya kami menerapkannya dengan cara yang mencapai ketidakmampuan dan fitur-fiturnya. CustomString dapat diimplementasikan tanpa membuatnya final menggunakan MarkerInterface yang akan memberikan perilaku khusus java untuk penyatuannya, fitur ini masih ditunggu!

Hai, India
sumber
0

Sebagian besar jawaban terkait dengan ketidakmampuan - mengapa objek tipe String tidak dapat diperbarui di tempat. Ada banyak diskusi yang baik di sini, dan masyarakat Jawa sebaiknya mengadopsi kekekalan sebagai kepala sekolah. (Tidak menahan napas.)

Namun pertanyaan OP adalah tentang mengapa itu final - mengapa tidak bisa diperpanjang. Beberapa di sini memang mengambil ini, tetapi saya akan setuju dengan OP bahwa ada celah nyata di sini. Bahasa lain memungkinkan pengembang membuat jenis nominal baru untuk suatu jenis. Sebagai contoh di Haskell saya dapat membuat tipe baru berikut ini yang identik pada saat run-time sebagai teks, tetapi memberikan keamanan-mengikat pada waktu kompilasi.

newtype AccountCode = AccountCode Text
newtype FundCode = FundCode Text

Jadi saya akan mengajukan saran berikut sebagai tambahan untuk bahasa Java:

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

(Atau mungkin kita bisa mulai melepaskan diri dari Jawa)

dsmith
sumber
-1

Jika Anda membuat string sekali Ini akan mempertimbangkan itu, itu adalah objek jika Anda ingin memodifikasi itu, itu tidak mungkin, itu akan membuat objek baru.

Suresh
sumber
Tolong jelaskan jawaban Anda.
Marko Popovic