Kita semua tahu bahwa String
itu tidak dapat diubah di Jawa, tetapi periksa kode berikut:
String s1 = "Hello World";
String s2 = "Hello World";
String s3 = s1.substring(6);
System.out.println(s1); // Hello World
System.out.println(s2); // Hello World
System.out.println(s3); // World
Field field = String.class.getDeclaredField("value");
field.setAccessible(true);
char[] value = (char[])field.get(s1);
value[6] = 'J';
value[7] = 'a';
value[8] = 'v';
value[9] = 'a';
value[10] = '!';
System.out.println(s1); // Hello Java!
System.out.println(s2); // Hello Java!
System.out.println(s3); // World
Mengapa program ini beroperasi seperti ini? Dan mengapa nilai s1
dans2
berubah, tetapi tidak s3
?
java
string
reflection
immutability
Darshan Patel
sumber
sumber
(Integer)1+(Integer)2=42
dengan mengacaukan autoboxing cache; (Disgruntled-Bomb-Java-Edition) ( thedailywtf.com/Articles/Disgruntled-Bomb-Java-Edition.aspx )Jawaban:
String
tidak dapat diubah * tetapi ini hanya berarti Anda tidak dapat mengubahnya menggunakan API publiknya.Apa yang Anda lakukan di sini adalah menghindari API normal, menggunakan refleksi. Dengan cara yang sama, Anda dapat mengubah nilai enum, mengubah tabel pencarian yang digunakan dalam bilangan bulat Integer dll.
Sekarang, alasan
s1
dans2
nilai perubahan, adalah bahwa keduanya merujuk ke string yang diinternir yang sama. Kompiler melakukan ini (sebagaimana disebutkan oleh jawaban lain).Alasannya
s3
sebenarnya tidak sedikit mengejutkan bagi saya, karena saya pikir itu akan berbagivalue
array ( itu dalam versi Java yang lebih lama , sebelum Java 7u6). Namun, melihat kode sumberString
, kita dapat melihat bahwavalue
array karakter untuk substring sebenarnya disalin (menggunakanArrays.copyOfRange(..)
). Inilah mengapa itu tidak berubah.Anda dapat menginstal
SecurityManager
, untuk menghindari kode berbahaya untuk melakukan hal-hal seperti itu. Tetapi perlu diingat bahwa beberapa perpustakaan bergantung pada penggunaan trik-trik refleksi semacam ini (biasanya alat ORM, perpustakaan AOP dll).*) Saya awalnya menulis bahwa
String
s tidak benar-benar abadi, hanya "tidak dapat diubah". Ini mungkin menyesatkan dalam implementasi saat iniString
, di manavalue
array memang ditandaiprivate final
. Namun, masih perlu dicatat bahwa tidak ada cara untuk mendeklarasikan array di Java sebagai tidak dapat diubah, jadi harus berhati-hati untuk tidak mengeksposnya di luar kelasnya, bahkan dengan pengubah akses yang tepat.Karena topik ini tampaknya sangat populer, inilah beberapa saran untuk bacaan lebih lanjut: Refleksi Madness Heinz Kabutz dari JavaZone 2009, yang mencakup banyak masalah di OP, bersama dengan refleksi lain ... well ... kegilaan.
Ini mencakup mengapa ini kadang berguna. Dan mengapa, sebagian besar waktu, Anda harus menghindarinya. :-)
sumber
String
magang adalah bagian dari JLS ( "string literal selalu merujuk ke instance yang sama dari class String" ). Tapi saya setuju, bukan praktik yang baik untuk mengandalkan detail implementasiString
kelas.substring
salinan daripada menggunakan "bagian" dari array yang ada, adalah sebaliknya jika saya memiliki string besars
dan mengeluarkan substring kecil yang dipanggilt
, dan saya kemudian ditinggalkans
tetapi disimpant
, maka array besar akan tetap hidup (bukan sampah yang dikumpulkan). Jadi mungkin lebih alami untuk setiap nilai string untuk memiliki array terkait sendiri?String
instance harus membawa variabel untuk mengingat offset ke array dan panjang yang dimaksud. Itu overhead untuk tidak mengabaikan mengingat jumlah total string dan rasio khas antara string normal dan substring dalam suatu aplikasi. Karena mereka harus dievaluasi untuk setiap operasi string, itu berarti memperlambat setiap operasi string hanya untuk kepentingan satu operasi saja, sebuah substring yang murah.byte[]
string untuk string ASCII danchar[]
bagi yang lain menyiratkan bahwa setiap operasi harus memeriksa jenis string yang sebelum Pengoperasian. Ini menghambat pengurutan kode ke dalam metode menggunakan string yang merupakan langkah pertama dari optimasi lebih lanjut menggunakan informasi konteks pemanggil. Ini dampak besar.Di Jawa, jika dua variabel primitif string diinisialisasi ke literal yang sama, itu menetapkan referensi yang sama untuk kedua variabel:
Itulah alasan perbandingan kembali benar. String ketiga dibuat dengan menggunakan
substring()
yang membuat string baru alih-alih menunjuk ke yang sama.Saat Anda mengakses string menggunakan refleksi, Anda mendapatkan pointer aktual:
Jadi perubahan ini akan mengubah string yang memegang pointer ke sana, tetapi karena
s3
dibuat dengan string baru karenasubstring()
itu tidak akan berubah.sumber
intern
secara manual pada String non-literal dan menuai manfaatnya.intern
bijaksana. Menginternir segala sesuatu tidak banyak menguntungkan Anda, dan dapat menjadi sumber dari beberapa momen menggaruk kepala saat Anda menambahkan refleksi ke dalam campuran.Test1
danTest1
tidak konsisten dengantest1==test2
dan tidak mengikuti konvensi penamaan java.Anda menggunakan refleksi untuk menghindari kekekalan String - ini adalah bentuk "serangan".
Ada banyak contoh yang dapat Anda buat seperti ini (misalnya Anda bahkan dapat membuat
Void
instance objek juga), tetapi itu tidak berarti bahwa String tidak "tidak dapat diubah".Ada kasus penggunaan di mana jenis kode ini dapat digunakan untuk keuntungan Anda dan menjadi "pengkodean yang baik", seperti membersihkan kata sandi dari memori pada saat yang paling dini (sebelum GC) .
Tergantung pada manajer keamanan, Anda mungkin tidak dapat mengeksekusi kode Anda.
sumber
Anda menggunakan refleksi untuk mengakses "detail implementasi" objek string. Kekekalan adalah fitur antarmuka publik dari suatu objek.
sumber
Pengubah visibilitas dan final (yaitu ketidakmampuan) bukan merupakan ukuran terhadap kode berbahaya di Jawa; mereka hanyalah alat untuk melindungi dari kesalahan dan untuk membuat kode lebih dapat dipertahankan (salah satu nilai jual besar dari sistem). Itulah sebabnya Anda dapat mengakses detail implementasi internal seperti array char dukungan untuk
String
melalui refleksi.Efek kedua yang Anda lihat adalah bahwa semua
String
berubah sementara sepertinya Anda hanya berubahs1
. Ini adalah properti tertentu dari Java String literal yang secara otomatis diinternir, yaitu di-cache. Dua String literal dengan nilai yang sama sebenarnya akan menjadi objek yang sama. Ketika Anda membuat sebuah String dengannew
itu tidak akan diinternir secara otomatis dan Anda tidak akan melihat efek ini.#substring
hingga baru-baru ini (Java 7u6) bekerja dengan cara yang serupa, yang akan menjelaskan perilaku dalam versi asli pertanyaan Anda. Itu tidak membuat array char backing baru tetapi menggunakan kembali dari String asli; itu hanya menciptakan objek String baru yang menggunakan offset dan panjang untuk menyajikan hanya sebagian dari array itu. Ini umumnya berfungsi sebagai String tidak dapat diubah - kecuali Anda menghindarinya. Properti ini dari#substring
juga berarti bahwa seluruh String asli tidak dapat berupa sampah yang dikumpulkan ketika substring yang lebih pendek dibuat darinya masih ada.Pada Java saat ini dan versi pertanyaan Anda saat ini tidak ada perilaku aneh
#substring
.sumber
final
bahkan melalui refleksi. Juga, sebagaimana disebutkan dalam jawaban lain, karena Java 7u6,#substring
tidak membagikan array.final
telah berubah dari waktu ke waktu ...: -O Menurut pembicaraan "Reflection Madness" oleh Heinz saya diposting di utas lainnya,final
berarti final di JDK 1.1, 1.3 dan 1.4, tetapi dapat dimodifikasi menggunakan refleksi menggunakan 1.2 selalu , dan dalam 1,5 dan 6 dalam banyak kasus ...final
bidang dapat diubah melaluinative
kode seperti yang dilakukan oleh kerangka kerja Serialisasi ketika membaca bidang contoh serial sertaSystem.setOut(…)
yang memodifikasiSystem.out
variabel akhir . Yang terakhir adalah fitur yang paling menarik karena refleksi dengan penggantian akses tidak dapat mengubahstatic final
bidang.String immutability berasal dari perspektif antarmuka. Anda menggunakan refleksi untuk memintas antarmuka dan secara langsung memodifikasi internal instance String.
s1
dans2
keduanya diubah karena keduanya ditugaskan ke instance String "intern" yang sama. Anda dapat menemukan sedikit lebih banyak tentang bagian itu dari artikel ini tentang kesetaraan string dan magang. Anda mungkin terkejut mengetahui bahwa dalam kode sampel Anda,s1 == s2
kembalitrue
!sumber
Versi Java mana yang Anda gunakan? Dari Java 1.7.0_06, Oracle telah mengubah representasi internal String, terutama substring.
Mengutip dari Oracle Tunes Internal String Representation Java :
Dengan perubahan ini, itu bisa terjadi tanpa refleksi (???).
sumber
Sebenarnya ada dua pertanyaan di sini:
Ke poin 1: Kecuali untuk ROM tidak ada memori yang tidak dapat diubah di komputer Anda. Saat ini bahkan ROM terkadang dapat ditulis. Selalu ada beberapa kode di suatu tempat (apakah itu kernel atau kode asli yang menghindari lingkungan terkelola Anda) yang dapat menulis ke alamat memori Anda. Jadi, dalam "kenyataan", tidak, mereka tidak mutlak abadi.
Ke poin 2: Ini karena substring mungkin mengalokasikan contoh string baru, yang kemungkinan menyalin array. Dimungkinkan untuk menerapkan substring sedemikian rupa sehingga tidak akan melakukan salinan, tetapi itu tidak berarti demikian. Ada pengorbanan yang terlibat.
Sebagai contoh, haruskah memegang referensi yang
reallyLargeString.substring(reallyLargeString.length - 2)
menyebabkan sejumlah besar memori tetap hidup, atau hanya beberapa byte?Itu tergantung pada bagaimana substring diimplementasikan. Salinan yang dalam akan menjaga lebih sedikit memori yang hidup, tetapi akan berjalan sedikit lebih lambat. Salinan dangkal akan membuat lebih banyak memori hidup, tetapi akan lebih cepat. Menggunakan salinan yang dalam juga dapat mengurangi fragmentasi tumpukan, karena objek string dan buffernya dapat dialokasikan dalam satu blok, sebagai lawan dari 2 alokasi tumpukan terpisah.
Bagaimanapun, sepertinya JVM Anda memilih untuk menggunakan salinan yang dalam untuk panggilan substring.
sumber
Untuk menambah jawaban @ haraldK - ini adalah peretasan keamanan yang dapat menyebabkan dampak serius pada aplikasi.
Hal pertama adalah modifikasi string konstan yang disimpan dalam String Pool. Ketika string dideklarasikan sebagai a
String s = "Hello World";
, string ditempatkan ke dalam kumpulan objek khusus untuk digunakan kembali secara potensial. Masalahnya adalah bahwa kompiler akan menempatkan referensi ke versi yang dimodifikasi pada waktu kompilasi dan setelah pengguna memodifikasi string yang disimpan dalam kumpulan ini saat runtime, semua referensi dalam kode akan menunjuk ke versi yang dimodifikasi. Ini akan menghasilkan bug berikut:Akan dicetak:
Ada masalah lain yang saya alami ketika saya menerapkan perhitungan berat atas string berisiko tersebut. Ada bug yang terjadi pada 1 dari 10.000 kali selama perhitungan yang membuat hasilnya tidak dapat ditentukan. Saya dapat menemukan masalah dengan mematikan JIT - Saya selalu mendapatkan hasil yang sama dengan JIT dimatikan. Dugaan saya adalah bahwa alasannya adalah peretasan keamanan String ini yang mematahkan beberapa kontrak optimasi JIT.
sumber
String.format("")
di dalam salah satu loop internal. Ada kemungkinan untuk itu menjadi beberapa-lain-kemudian-masalah JIT-kegagalan, tapi saya percaya itu JIT, karena masalah ini tidak pernah direproduksi lagi setelah menambahkan no-op ini.String
dalam, tidak masuk ke pikiran Anda?final
jaminan keselamatan ulir bidang yang rusak saat memodifikasi data setelah konstruksi objek. Jadi Anda bisa melihatnya sebagai masalah JIT atau masalah MT sesuka Anda. Masalah sebenarnya adalah meretasString
dan memodifikasi data yang diharapkan tidak berubah.Menurut konsep pooling, semua variabel String yang berisi nilai yang sama akan mengarah ke alamat memori yang sama. Oleh karena itu s1 dan s2, keduanya mengandung nilai yang sama "Hello World", akan mengarah ke lokasi memori yang sama (katakanlah M1).
Di sisi lain, s3 berisi "Dunia", maka itu akan menunjuk ke alokasi memori yang berbeda (katakanlah M2).
Jadi sekarang yang terjadi adalah bahwa nilai S1 sedang diubah (dengan menggunakan nilai char []). Jadi nilai di lokasi memori M1 yang ditunjukkan oleh s1 dan s2 telah berubah.
Oleh karena itu, lokasi memori M1 telah dimodifikasi yang menyebabkan perubahan nilai s1 dan s2.
Tetapi nilai lokasi M2 tetap tidak berubah, karenanya s3 berisi nilai asli yang sama.
sumber
Alasan s3 tidak benar-benar berubah adalah karena di Jawa ketika Anda melakukan substring, array karakter nilai untuk substring disalin secara internal (menggunakan Arrays.copyOfRange ()).
s1 dan s2 adalah sama karena di Jawa keduanya merujuk ke string yang diinternir yang sama. Ini dengan desain di Jawa.
sumber
String.substring(int, int)
diubah dengan Java 7u6. Sebelum 7u6, JVM hanya akan menyimpan pointer ke asliString
'schar[]
bersama dengan indeks dan panjang. Setelah 7u6, ia menyalin substring ke baru.String
Ada pro dan kontra.String tidak dapat diubah, tetapi melalui refleksi Anda diizinkan untuk mengubah kelas String. Anda baru saja mendefinisikan ulang kelas String sebagai bisa berubah secara real-time. Anda dapat mendefinisikan kembali metode menjadi publik atau pribadi atau statis jika Anda inginkan.
sumber
[Penafian ini adalah gaya jawaban yang sengaja dikemukakan pendapat karena saya merasa lebih "jangan lakukan ini di rumah anak-anak" jawaban diperlukan]
Dosa adalah garis
field.setAccessible(true);
yang mengatakan melanggar api publik dengan mengizinkan akses ke bidang pribadi. Itulah lubang keamanan raksasa yang dapat dikunci dengan mengkonfigurasi manajer keamanan.Fenomena dalam pertanyaan ini adalah detail implementasi yang tidak akan pernah Anda lihat ketika tidak menggunakan baris kode berbahaya tersebut untuk melanggar pengubah akses melalui refleksi. Jelas dua (biasanya) string yang tidak dapat diubah dapat berbagi array char yang sama. Apakah substring berbagi array yang sama tergantung pada apakah substring dapat dan apakah pengembang berpikir untuk membagikannya. Biasanya ini adalah detail implementasi yang tidak terlihat yang seharusnya tidak perlu Anda ketahui kecuali Anda memotret pengubah akses melalui kepala dengan baris kode tersebut.
Ini bukan ide yang baik untuk mengandalkan detail yang tidak bisa dialami tanpa melanggar pengubah akses menggunakan refleksi. Pemilik kelas itu hanya mendukung API publik normal dan bebas untuk membuat perubahan implementasi di masa mendatang.
Setelah mengatakan semua itu, baris kode benar-benar sangat berguna ketika Anda memegang pistol di kepala Anda dan memaksa Anda untuk melakukan hal-hal berbahaya seperti itu. Menggunakan pintu belakang itu biasanya merupakan bau kode yang perlu Anda tingkatkan ke kode perpustakaan yang lebih baik di mana Anda tidak perlu berbuat dosa. Penggunaan umum lain dari baris kode berbahaya itu adalah untuk menulis "kerangka kerja voodoo" (orm, container injeksi, ...). Banyak orang menjadi religius tentang kerangka kerja seperti itu (baik untuk dan melawan mereka) jadi saya akan menghindari mengundang perang api dengan mengatakan tidak lain dari sebagian besar programmer tidak harus pergi ke sana.
sumber
String dibuat di area permanen memori tumpukan JVM. Jadi ya, itu benar-benar abadi dan tidak dapat diubah setelah dibuat. Karena dalam JVM, ada tiga jenis memori tumpukan: 1. Generasi muda 2. Generasi tua 3. Generasi permanen.
Ketika objek apa pun dibuat, ia masuk ke area tumpukan generasi muda dan area PermGen dicadangkan untuk pengumpulan string.
Berikut ini detail lebih lanjut yang bisa Anda tuju dan dapatkan lebih banyak informasi dari: Bagaimana Pengumpulan Sampah bekerja di Jawa .
sumber
String bersifat abadi karena tidak ada metode untuk memodifikasi objek String. Itulah alasan mereka diperkenalkan StringBuilder dan StringBuffer kelas
sumber