Apa yang akan terjadi jika java.lang.String tidak final?

16

Saya sudah lama pengembang Java dan akhirnya, setelah mengambil jurusan, saya punya waktu untuk mempelajarinya dengan baik untuk mengikuti ujian sertifikasi ... Satu hal yang selalu mengganggu saya adalah String menjadi "final". Saya mengerti ketika membaca tentang masalah keamanan dan hal-hal terkait ... Tapi, serius, apakah ada yang punya contoh nyata tentang itu?

Misalnya, apa yang akan terjadi jika String tidak final? Sepertinya tidak ada di Ruby. Saya belum pernah mendengar keluhan yang datang dari komunitas Ruby ... Dan saya mengetahui StringUtils dan kelas terkait yang harus Anda implementasikan sendiri atau cari melalui web untuk hanya menerapkan perilaku itu (4 baris kode) yang Anda bersedia.

wleao
sumber
9
Anda mungkin tertarik dengan pertanyaan ini di Stack Overflow: stackoverflow.com/questions/2068804/why-is-string-final-in-java
Thomas Owens
Demikian pula, apa yang akan terjadi jika java.io.Filetidak final. Oh, tidak. Bersetubuh.
Tom Hawtin - tackline
1
Akhir dari peradaban barat seperti yang kita kenal.
Tulains Córdova

Jawaban:

22

Alasan utamanya adalah kecepatan: finalkelas tidak dapat diperpanjang yang memungkinkan JIT melakukan semua jenis optimisasi saat menangani string - tidak pernah perlu memeriksa metode yang diganti.

Alasan lain adalah keamanan thread: Immutables selalu aman karena thread harus benar-benar membangunnya sebelum dapat diteruskan ke orang lain - dan setelah membangun, mereka tidak dapat diubah lagi.

Juga, para penemu runtime Java selalu ingin berbuat salah di sisi keamanan. Mampu memperluas String (sesuatu yang sering saya lakukan di Groovy karena sangat nyaman) dapat membuka seluruh kaleng cacing jika Anda tidak tahu apa yang Anda lakukan.

Aaron Digulla
sumber
2
Itu jawaban yang salah. Selain untuk verifikasi yang diperlukan oleh spesifikasi JVM, HotSpot hanya menggunakan finalsebagai pemeriksaan cepat bahwa suatu metode tidak diganti ketika mengkompilasi kode.
Tom Hawtin - tackline
1
@ TomHawtin-tackline: Referensi?
Aaron Digulla
1
Anda tampaknya menyiratkan bahwa kekekalan dan menjadi final entah bagaimana terkait. "Alasan lain adalah keamanan thread: Immutables selalu aman thread ...". Alasan suatu objek tidak berubah atau tidak adalah karena mereka adalah atau tidak final.
Tulains Córdova
1
Selain itu, kelas String tidak dapat diubah terlepas dari finalkata kunci di kelas. Array karakter yang dikandungnya adalah final, membuatnya tidak berubah. Membuat kelas itu sendiri akhir menutup loop sebagai @abl menyebutkan karena subkelas yang dapat diubah tidak dapat diperkenalkan.
1
@Snowman Lebih tepatnya, array karakter menjadi final hanya membuat referensi array tidak berubah, bukan isi array.
Michał Kosmulski
10

Ada alasan lain mengapa Stringkelas Java harus final: penting untuk keamanan dalam beberapa skenario. Jika Stringkelas tidak final, kode berikut akan rentan terhadap serangan halus oleh penelepon jahat:

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

Serangan: penelepon jahat dapat membuat subkelas String EvilString,, di mana EvilString.startsWith()selalu mengembalikan true tetapi di mana nilai EvilStringadalah sesuatu yang jahat (misalnya, javascript:alert('xss')). Karena subclassing, ini akan menghindari pemeriksaan keamanan. Serangan ini dikenal sebagai kerentanan waktu-ke-waktu-ke-waktu-penggunaan (TOCTTOU): antara waktu ketika pemeriksaan dilakukan (bahwa url dimulai dengan http / https) dan waktu ketika nilai digunakan (untuk membuat cuplikan html), nilai efektif dapat berubah. Jika Stringtidak final, maka risiko TOCTTOU akan meluas.

Jadi, jika Stringbelum final, sulit untuk menulis kode aman jika Anda tidak dapat mempercayai penelepon Anda. Tentu saja, itulah posisi perpustakaan Java: mereka mungkin dipanggil oleh applet yang tidak tepercaya, sehingga mereka tidak berani mempercayai penelepon mereka. Ini berarti sulit untuk menulis kode perpustakaan aman, jika Stringtidak final.

DW
sumber
Tentu saja, masih memungkinkan untuk melakukan itu TOCTTOU vuln dengan menggunakan refleksi untuk memodifikasi bagian char[]dalam string, tetapi membutuhkan sedikit keberuntungan dalam pengaturan waktu.
Peter Taylor
2
@ Peter Taylor, ini lebih rumit dari itu. Anda hanya dapat menggunakan refleksi untuk mengakses variabel pribadi jika SecurityManager memberi Anda izin untuk melakukannya. Applet dan kode tidak tepercaya lainnya tidak diberikan izin ini, dan karenanya tidak dapat menggunakan refleksi untuk memodifikasi pribadi char[]di dalam string; oleh karena itu, mereka tidak dapat mengeksploitasi kerentanan TOCTTOU.
DW
Saya tahu, tetapi itu masih menyisakan aplikasi dan applet yang ditandatangani - dan berapa banyak orang yang benar-benar takut dengan dialog peringatan untuk applet yang ditandatangani sendiri?
Peter Taylor
2
@ Peter, bukan itu intinya. Intinya adalah bahwa menulis perpustakaan Java tepercaya akan jauh lebih sulit, dan akan ada lebih banyak kerentanan yang dilaporkan di perpustakaan Java, jika Stringitu belum final. Selama beberapa waktu model ancaman ini relevan, maka menjadi penting untuk dipertahankan. Selama penting untuk applet dan kode tidak dipercaya lainnya, itu bisa dibilang cukup untuk membenarkan pembuatan Stringfinal, bahkan jika itu tidak diperlukan untuk aplikasi tepercaya. (Bagaimanapun, aplikasi tepercaya sudah dapat melewati semua langkah-langkah keamanan, terlepas dari apakah itu final.)
DW
"Serangan: penelepon jahat dapat membuat subkelas String, EvilString, di mana EvilString.startsWith () selalu mengembalikan true ..." - tidak jika beginWith () bersifat final.
Erik
4

Jika tidak, aplikasi java multithreaded akan berantakan (bahkan lebih buruk dari yang sebenarnya).

IMHO Keuntungan utama dari string akhir (tidak dapat diubah) adalah bahwa mereka secara inheren thread-safe: mereka tidak memerlukan sinkronisasi (menulis kode itu kadang-kadang cukup sepele tetapi lebih dari kurang jauh dari itu). Jika itu bisa berubah, jaminan hal-hal seperti toolkit windows multithread akan sangat sulit, dan hal-hal itu sangat diperlukan.

Randolf Rincón Fadul
sumber
3
finalkelas tidak ada hubungannya dengan sifat tidak menentu kelas.
Jawaban ini tidak menjawab pertanyaan. -1
FUZxxl
3
Jarrod Roberson: jika kelasnya belum final, Anda dapat membuat String yang bisa berubah menggunakan pewarisan.
ysdx
@JarrodRoberson akhirnya seseorang memperhatikan kesalahan tersebut. Jawaban yang diterima juga menyiratkan akhir sama dengan abadi.
Tulains Córdova
1

Keuntungan lain untuk Stringmenjadi final adalah memastikan hasil yang dapat diprediksi, seperti halnya kekekalan. String, meskipun sebuah kelas, dimaksudkan untuk diperlakukan dalam beberapa skenario, seperti tipe nilai (kompilator bahkan mendukungnya melalui String blah = "test"). Oleh karena itu, jika Anda membuat string dengan nilai tertentu, Anda mengharapkannya memiliki perilaku tertentu yang mewarisi string apa pun, mirip dengan harapan yang Anda miliki dengan bilangan bulat.

Pikirkan tentang ini: Bagaimana jika Anda sub-kelas java.lang.Stringdan over-rode equalsatau hashCodemetode? Tiba-tiba dua Strings "test" tidak bisa lagi sama satu sama lain.

Bayangkan kekacauan jika saya bisa melakukan ini:

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

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

beberapa kelas lain:

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
}
Brandon
sumber
1

Latar Belakang

Ada tiga tempat di mana final dapat muncul di Jawa:

Membuat final kelas mencegah semua subklasifikasi kelas. Membuat metode akhir mencegah subclass dari metode menimpanya. Membuat field final mencegahnya agar tidak diubah nanti.

Kesalahpahaman

Ada optimasi yang terjadi di sekitar metode dan bidang akhir.

Metode terakhir memudahkan HotSpot untuk mengoptimalkan melalui inlining. Namun, HotSpot melakukan ini bahkan jika metode ini tidak final karena berfungsi dengan asumsi bahwa itu belum ditimpa sampai dibuktikan sebaliknya. Lebih lanjut tentang ini di SO

Variabel terakhir dapat dioptimalkan secara agresif, dan lebih lanjut tentang itu dapat dibaca di bagian JLS 17.5.3 .

Namun, dengan pemahaman itu orang harus sadar bahwa tak satu pun dari optimasi ini adalah tentang membuat kelas final. Tidak ada perolehan kinerja dengan membuat final kelas.

Aspek terakhir dari suatu kelas tidak ada hubungannya dengan ketidakberubahan juga. Seseorang dapat memiliki kelas yang tidak dapat diubah (seperti BigInteger ) yang tidak final, atau kelas yang bisa berubah dan final (seperti StringBuilder ). Keputusan tentang apakah kelas harus final adalah masalah desain.

Desain Akhir

String adalah salah satu tipe data yang paling banyak digunakan. Mereka ditemukan sebagai kunci untuk peta, mereka menyimpan nama pengguna dan kata sandi, itu adalah apa yang Anda baca dari keyboard atau bidang pada halaman web. String ada di mana - mana .

Peta

Hal pertama yang perlu dipertimbangkan tentang apa yang akan terjadi jika Anda dapat mensubktrasikan String adalah menyadari bahwa seseorang dapat membuat kelas String yang bisa berubah yang akan tampak seperti String. Ini akan mengacaukan Peta di mana-mana.

Pertimbangkan kode hipotetis ini:

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");

Ini adalah masalah dengan menggunakan kunci yang bisa berubah-ubah secara umum dengan Peta, tetapi hal yang saya coba dapatkan di sana adalah bahwa tiba-tiba sejumlah hal tentang istirahat Peta. Entri tidak lagi berada di tempat yang tepat di peta. Dalam HashMap, nilai hash telah (seharusnya) berubah dan dengan demikian tidak lagi berada di entri yang benar. Di TreeMap, pohon sekarang rusak karena salah satu node berada di sisi yang salah.

Karena menggunakan String untuk kunci-kunci ini sangat umum, perilaku ini harus dicegah dengan membuat String menjadi final.

Anda mungkin tertarik membaca Mengapa String tidak dapat diubah di Jawa? untuk lebih lanjut tentang sifat abadi String.

String jahat

Ada sejumlah opsi jahat untuk Strings. Pertimbangkan jika saya membuat sebuah String yang selalu mengembalikan nilai true ketika equal dipanggil ... dan meneruskannya ke pemeriksaan kata sandi? Atau membuatnya agar penugasan ke MyString akan mengirim salinan String ke beberapa alamat email?

Ini adalah kemungkinan yang sangat nyata ketika Anda memiliki kemampuan untuk membuat subkelas String.

Optimalisasi Java.lang String

Sementara sebelumnya saya menyebutkan bahwa final tidak membuat String lebih cepat. Namun, kelas String (dan kelas-kelas lain di java.lang) sering menggunakan perlindungan tingkat paket dari bidang dan metode untuk memungkinkan java.langkelas - kelas lain dapat bermain-main dengan internal daripada melalui API publik untuk String sepanjang waktu. Fungsi seperti getChars tanpa pengecekan rentang atau lastIndexOf yang digunakan oleh StringBuffer, atau konstruktor yang berbagi array yang mendasarinya (perhatikan bahwa Java 6 merupakan hal yang diubah karena masalah memori).

Jika seseorang membuat subkelas String, itu tidak akan dapat membagikan optimasi tersebut (kecuali jika itu bagian dari java.langjuga, tapi itu paket yang disegel ).

Lebih sulit untuk merancang sesuatu untuk ekstensi

Merancang sesuatu yang bisa diperpanjang itu sulit . Ini berarti bahwa Anda harus mengekspos bagian internal Anda untuk hal lain agar dapat dimodifikasi.

String yang dapat diperpanjang tidak dapat memiliki kebocoran ingatannya diperbaiki. Bagian-bagian itu perlu terkena subclass dan mengubah kode itu berarti subclass akan rusak.

Java bangga dengan kompatibilitas mundur dan dengan membuka kelas inti untuk ekstensi, seseorang kehilangan beberapa kemampuan untuk memperbaiki hal-hal sambil tetap mempertahankan kemampuan komputasi dengan subkelas pihak ketiga.

Checkstyle memiliki aturan yang diberlakukan (yang benar-benar membuat saya frustrasi ketika menulis kode internal) yang disebut "DesignForExtension" yang memberlakukan bahwa setiap kelas adalah:

  • Abstrak
  • Terakhir
  • Implementasi kosong

Yang rasional untuk itu adalah:

Gaya desain API ini melindungi kacamata super agar tidak rusak oleh subclass. The downside adalah bahwa subclass terbatas dalam fleksibilitas mereka, khususnya mereka tidak dapat mencegah pelaksanaan kode di superclass, tetapi itu juga berarti bahwa subclass tidak dapat merusak keadaan superclass dengan lupa memanggil metode super.

Mengizinkan perluasan kelas implementasi berarti bahwa subclass mungkin dapat merusak keadaan kelas yang menjadi dasarnya dan membuatnya sehingga berbagai jaminan yang diberikan superclass tidak valid. Untuk sesuatu yang kompleks seperti String, hampir pasti bahwa mengubah bagian itu akan merusak sesuatu.

Pengembang hurbis

Itu bagian dari menjadi pengembang. Pertimbangkan kemungkinan bahwa setiap pengembang akan membuat subclass String mereka sendiri dengan koleksi utilities mereka sendiri di dalamnya. Tetapi sekarang subkelas ini tidak dapat secara bebas ditugaskan kembali satu sama lain.

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

Cara ini menyebabkan kegilaan. Casting ke String di semua tempat dan memeriksa untuk melihat apakah String adalah turunan dari kelas String Anda atau jika tidak, membuat String baru berdasarkan yang itu dan ... hanya, tidak. Jangan.

Saya yakin Anda dapat menulis kelas String yang baik ... tetapi menulis beberapa implementasi string kepada orang-orang gila yang menulis C ++ dan harus berurusan dengan std::stringdan char*dan sesuatu dari boost dan SString , dan yang lainnya .

Java String magic

Ada beberapa hal ajaib yang dilakukan Java dengan String. Ini membuatnya lebih mudah bagi seorang programmer untuk berurusan dengan tetapi memperkenalkan beberapa inkonsistensi dalam bahasa. Mengizinkan subclass pada String akan mengambil beberapa pemikiran yang sangat signifikan tentang bagaimana menangani hal-hal ajaib ini:

  • String Literals (JLS 3.10.5 )

    Memiliki kode yang memungkinkan seseorang untuk melakukan:

    String foo = "foo";

    Ini tidak harus bingung dengan tinju dari jenis numerik seperti Integer. Anda tidak dapat melakukannya 1.toString(), tetapi Anda dapat melakukannya "foo".concat(bar).

  • The +operator (JLS 15.18.1 )

    Tidak ada jenis referensi lain di Jawa yang memungkinkan operator untuk menggunakannya. String itu spesial. Operator concatenation string juga bekerja pada level kompiler sehingga "foo" + "bar"menjadi "foobar"saat dikompilasi daripada saat runtime.

  • Konversi String (JLS 5.1.11 )

    Semua objek dapat dikonversi menjadi String hanya dengan menggunakannya dalam konteks String.

  • String Interning ( JavaDoc )

    Kelas String memiliki akses ke kumpulan String yang memungkinkannya untuk memiliki representasi kanonik objek yang dihuni pada tipe kompilasi dengan String literal.

Mengizinkan subkelas String akan berarti bahwa bit-bit ini dengan String yang membuatnya lebih mudah diprogram akan menjadi sangat sulit atau tidak mungkin dilakukan ketika jenis-jenis String lainnya dimungkinkan.

Komunitas
sumber
0

Jika String belum final, setiap peti alat programmer akan berisi "String hanya dengan beberapa metode pembantu yang bagus". Masing-masing akan tidak kompatibel dengan semua peti alat lainnya.

Saya menganggapnya sebagai pembatasan konyol. Hari ini saya yakin ini adalah keputusan yang sangat masuk akal. Itu membuat Strings menjadi Strings.


sumber
finaldi Jawa bukan hal yang sama seperti finaldi C #. Dalam konteks ini, itu sama dengan C # readonly. Lihat stackoverflow.com/questions/1327544/…
Arseni Mourzenko
9
@MainMa: Sebenarnya, dalam konteks ini sepertinya sama dengan C # yang disegel, seperti pada public final class String.
konfigurator