Saya tidak bisa mengerti alasannya. Saya selalu menggunakan kelas String seperti pengembang lain, tetapi ketika saya memodifikasi nilai itu, contoh baru dari String dibuat.
Apa yang mungkin menjadi alasan ketidakmampuan untuk kelas String di Jawa?
Saya tahu ada beberapa alternatif seperti StringBuffer atau StringBuilder. Itu hanya rasa ingin tahu.
Jawaban:
Konkurensi
Java didefinisikan dari awal dengan pertimbangan konkurensi. Seperti yang sering disebutkan, mutable yang dibagikan bermasalah. Satu hal dapat mengubah yang lain di belakang utas lainnya tanpa utas itu menyadarinya.
Ada sejumlah bug C ++ multithreaded yang telah muncul karena string yang dibagi - di mana satu modul berpikir aman untuk berubah ketika modul lain dalam kode telah menyimpan pointer ke sana dan berharap agar tetap sama.
'Solusi' untuk ini adalah bahwa setiap kelas membuat salinan defensif dari objek yang bisa berubah yang diteruskan ke sana. Untuk string yang dapat berubah, ini adalah O (n) untuk membuat salinan. Untuk string yang tidak dapat diubah, membuat salinan adalah O (1) karena itu bukan salinan, itu adalah objek yang sama yang tidak dapat diubah.
Dalam lingkungan multithreaded, objek yang tidak dapat diubah selalu dapat dibagi dengan aman antara satu sama lain. Ini mengarah pada pengurangan keseluruhan dalam penggunaan memori dan meningkatkan caching memori.
Keamanan
Banyak kali string diteruskan sebagai argumen untuk konstruktor - koneksi jaringan dan protocals adalah dua yang paling mudah terlintas dalam pikiran. Mampu mengubah ini pada waktu yang tidak ditentukan kemudian dalam eksekusi dapat menyebabkan masalah keamanan (fungsi berpikir itu terhubung ke satu mesin, tetapi dialihkan ke yang lain, tetapi segala sesuatu di objek terlihat seperti terhubung ke yang pertama ... bahkan string yang sama).
Java memungkinkan satu menggunakan refleksi - dan parameter untuk ini adalah string. Bahaya dari seseorang yang melewatkan string yang bisa dimodifikasi melalui jalan ke metode lain yang mencerminkan. Ini sangat buruk.
Kunci untuk Hash
Tabel hash adalah salah satu struktur data yang paling banyak digunakan. Kunci untuk struktur data sangat sering berupa string. Memiliki string yang tidak dapat diubah berarti bahwa (seperti di atas) tabel hash tidak perlu membuat salinan kunci hash setiap kali. Jika string bisa berubah, dan tabel hash tidak membuat ini, itu mungkin untuk sesuatu untuk mengubah kunci hash di kejauhan.
Cara Obyek di java bekerja, adalah bahwa semuanya memiliki kunci hash (diakses melalui metode hashCode ()). Memiliki string yang tidak dapat diubah berarti kode hash dapat di-cache. Mempertimbangkan seberapa sering String digunakan sebagai kunci hash, ini memberikan dorongan kinerja yang signifikan (daripada harus menghitung ulang kode hash setiap kali).
Substring
Dengan membuat String tidak berubah, array karakter yang mendasari yang mendukung struktur data juga tidak berubah. Hal ini memungkinkan untuk optimasi tertentu pada
substring
metode yang dilakukan (tidak harus dilakukan - juga memperkenalkan kemungkinan beberapa kebocoran memori juga).Jika kamu melakukan:
Nilai
bar
adalah 'mil'. Namun, keduanyafoo
danbar
dapat didukung oleh array karakter yang sama, mengurangi instantiation dari lebih banyak array karakter atau menyalinnya - hanya menggunakan titik awal dan akhir yang berbeda dalam string.Sekarang, kelemahan dari itu (kebocoran memori) adalah bahwa jika seseorang memiliki string panjang 1k dan mengambil substring dari karakter pertama dan kedua, itu juga akan didukung oleh array karakter panjang 1k. Array ini akan tetap ada di memori bahkan jika string asli yang memiliki nilai seluruh array karakter adalah sampah yang dikumpulkan.
Orang dapat melihat ini dalam String dari JDK 6b14 (kode berikut ini dari sumber GPL v2 dan digunakan sebagai contoh)
Perhatikan bagaimana substring menggunakan konstruktor String tingkat paket yang tidak melibatkan penyalinan array dan akan jauh lebih cepat (dengan mengorbankan kemungkinan menjaga sekitar beberapa array besar - meskipun tidak menduplikasi array besar juga).
Perhatikan bahwa kode di atas adalah untuk Java 1.6. Cara konstruktor substring diimplementasikan telah diubah dengan Java 1.7 seperti yang didokumentasikan dalam Changes to String representasi internal yang dibuat di Java 1.7.0_06 - masalah bing yang kehabisan memori kebocoran yang saya sebutkan di atas. Java kemungkinan tidak dilihat sebagai bahasa dengan banyak manipulasi String dan karenanya peningkatan kinerja untuk substring adalah hal yang baik. Sekarang, dengan dokumen XML besar yang disimpan dalam string yang tidak pernah dikumpulkan, ini menjadi masalah ... dan dengan demikian perubahan menjadi
String
tidak menggunakan array mendasar yang sama dengan substring, sehingga array karakter yang lebih besar dapat dikumpulkan lebih cepat.Jangan menyalahgunakan Stack
Seseorang dapat meneruskan nilai string di sekitar daripada referensi ke string yang tidak dapat diubah untuk menghindari masalah dengan kemampuan berubah-ubah. Namun, dengan string besar, meneruskan ini pada stack akan menjadi ... kasar ke sistem (menempatkan seluruh dokumen xml sebagai string pada stack dan kemudian melepasnya atau melanjutkan untuk meneruskannya ...).
Kemungkinan deduplikasi
Memang, ini bukan motivasi awal untuk mengapa String harus abadi, tetapi ketika seseorang melihat rasional mengapa String abadi adalah hal yang baik, ini tentu saja sesuatu yang perlu dipertimbangkan.
Siapa pun yang pernah bekerja dengan Strings sedikit tahu bahwa mereka dapat menyedot memori. Ini terutama benar ketika Anda melakukan hal-hal seperti menarik data dari database yang bertahan sebentar. Berkali-kali dengan sengatan ini, mereka adalah string yang sama berulang-ulang (satu kali untuk setiap baris).
Dengan Java 8 pembaruan 20, JEP 192 (motivasi dikutip di atas) sedang dilaksanakan untuk mengatasi ini. Tanpa masuk ke dalam perincian tentang bagaimana deduplikasi string bekerja, adalah penting bahwa String itu sendiri tidak dapat diubah. Anda tidak dapat menduplikat StringBuilders karena mereka dapat berubah dan Anda tidak ingin seseorang mengubah sesuatu dari bawah Anda. Immutable Strings (terkait dengan string pool) berarti Anda dapat melewati dan jika Anda menemukan dua string yang sama, Anda dapat mengarahkan satu string referensi ke yang lain dan membiarkan pengumpul sampah mengkonsumsi yang baru saja tidak digunakan.
Bahasa lainnya
Objective C (yang mendahului Java) memiliki
NSString
danNSMutableString
.C # dan .NET membuat pilihan desain yang sama dari string default menjadi tidak berubah.
String Lua juga tidak berubah.
Python juga.
Secara historis, Lisp, Skema, Smalltalk semua magang string dan dengan demikian membuatnya tidak berubah. Bahasa dinamis yang lebih modern sering menggunakan string dengan cara yang mengharuskan mereka tidak dapat diubah (mungkin bukan String , tetapi tidak dapat diubah).
Kesimpulan
Pertimbangan desain ini telah dibuat berulang kali dalam banyak bahasa. Ini adalah konsensus umum bahwa string tidak dapat diubah, untuk semua kecanggungan mereka, lebih baik daripada alternatif dan mengarah pada kode yang lebih baik (lebih sedikit bug) dan lebih cepat dieksekusi secara keseluruhan.
sumber
Alasan yang saya ingat:
Fasilitas String Pool tanpa membuat string tidak dapat diubah sama sekali tidak mungkin karena dalam kasus string pool satu objek string / literal misalnya "XYZ" akan dirujuk oleh banyak variabel referensi, jadi jika salah satu dari mereka mengubah nilai yang lain akan secara otomatis terpengaruh .
String telah banyak digunakan sebagai parameter untuk banyak kelas java misalnya untuk membuka koneksi jaringan, untuk membuka koneksi database, membuka file. Jika String tidak dapat diubah, ini akan mengarah pada ancaman keamanan serius.
Immutability memungkinkan String untuk me-cache hashcode-nya.
Menjadikannya aman-utas.
sumber
1) String Pool
Desainer Java tahu bahwa String akan menjadi tipe data yang paling banyak digunakan di semua jenis aplikasi Java dan itulah sebabnya mereka ingin mengoptimalkan dari awal. Salah satu langkah kunci ke arah itu adalah ide untuk menyimpan String literal di kolam String. Tujuannya adalah untuk mengurangi objek String sementara dengan membaginya dan untuk berbagi, mereka harus berasal dari kelas Immutable. Anda tidak dapat berbagi objek yang bisa berubah dengan dua pihak yang tidak saling kenal. Mari kita ambil contoh hipotetis, di mana dua variabel referensi menunjuk ke objek String yang sama:
Sekarang jika s1 mengubah objek dari "Java" ke "C ++", variabel referensi juga mendapat nilai s2 = "C ++", yang bahkan tidak diketahuinya. Dengan membuat String tidak berubah, pembagian String literal ini dimungkinkan. Singkatnya, ide kunci String pool tidak dapat diimplementasikan tanpa membuat String final atau Immutable di Jawa.
2) Keamanan
Java memiliki tujuan yang jelas dalam hal menyediakan lingkungan yang aman di setiap tingkat layanan dan String sangat penting dalam hal-hal keamanan keseluruhan. String telah banyak digunakan sebagai parameter untuk banyak kelas Java, misalnya untuk membuka koneksi jaringan, Anda dapat melewatkan host dan port sebagai String, untuk membaca file di Jawa Anda dapat melewati jalur file dan direktori sebagai String dan untuk membuka koneksi database, Anda dapat meneruskan URL basis data sebagai String. Jika String tidak dapat diubah, pengguna mungkin telah diberikan untuk mengakses file tertentu dalam sistem, tetapi setelah otentikasi ia dapat mengubah PATH ke sesuatu yang lain, ini dapat menyebabkan masalah keamanan yang serius. Demikian pula, saat terhubung ke database atau mesin lain di jaringan, mengubah nilai String dapat menimbulkan ancaman keamanan. String yang dapat berubah juga dapat menyebabkan masalah keamanan dalam Refleksi juga,
3) Penggunaan String dalam Mekanisme Memuat Kelas
Alasan lain untuk membuat String final atau Immutable didorong oleh fakta bahwa itu banyak digunakan dalam mekanisme pemuatan kelas. Karena String tidak dapat diubah, penyerang dapat mengambil keuntungan dari fakta ini dan permintaan untuk memuat kelas Java standar misalnya java.io.Reader dapat diubah menjadi kelas berbahaya com.unknown.DataStolenReader. Dengan menjaga agar String tetap final dan tidak berubah, setidaknya kita bisa memastikan bahwa JVM memuat kelas yang benar.
4) Manfaat Multithreading
Karena Concurrency dan Multi-threading adalah penawaran utama Java, sangat masuk akal untuk memikirkan keamanan benang dari objek String. Karena diharapkan bahwa String akan digunakan secara luas, menjadikannya Immutable berarti tidak ada sinkronisasi eksternal, berarti kode yang jauh lebih bersih yang melibatkan pembagian String di antara banyak utas. Fitur tunggal ini, membuat penyandian konkurensi yang sudah rumit, membingungkan dan rentan kesalahan jauh lebih mudah. Karena String tidak dapat diubah dan kami hanya membaginya di antara utas, itu menghasilkan kode yang lebih mudah dibaca.
5) Optimasi dan Kinerja
Sekarang ketika Anda membuat kelas tidak dapat diubah, Anda tahu sebelumnya bahwa, kelas ini tidak akan berubah setelah dibuat. Ini menjamin jalur terbuka untuk banyak optimasi kinerja, misalnya caching. String sendiri tahu itu, saya tidak akan berubah, jadi String cache kode hash-nya. Bahkan menghitung kode hash dengan malas dan sekali dibuat, cukup cache saja. Dalam dunia sederhana, ketika Anda pertama kali memanggil metode hashCode () dari objek String apa pun, ia menghitung kode hash dan semua panggilan selanjutnya ke hashCode () mengembalikan nilai yang sudah dihitung dan di-cache. Ini menghasilkan peningkatan kinerja yang baik, mengingat String banyak digunakan dalam Maps berbasis hash misalnya Hashtable dan HashMap. Caching kode hash tidak dimungkinkan tanpa membuatnya tidak dapat diubah dan final, karena ini tergantung pada konten dari String itu sendiri.
sumber
Java Virtual Machine melakukan beberapa optimasi terkait operasi string yang tidak dapat dilakukan sebaliknya. Misalnya, jika Anda memiliki string dengan nilai "Mississippi" dan Anda menetapkan "Mississippi" .substring (0, 4) ke string lain, sejauh yang Anda tahu, salinan dibuat dari empat karakter pertama untuk membuat "Miss" . Apa yang tidak Anda ketahui adalah bahwa keduanya memiliki string asli "Mississippi" yang sama dengan yang satu menjadi pemilik dan yang lainnya menjadi referensi dari string itu dari posisi 0 hingga 4. (Referensi ke pemilik mencegah pemilik dikumpulkan oleh pemulung ketika pemiliknya keluar dari ruang lingkup)
Ini sepele untuk string sekecil "Mississippi", tetapi dengan string yang lebih besar dan beberapa operasi, tidak harus menyalin string adalah penghemat waktu yang besar! Jika string dapat diubah, maka Anda tidak dapat melakukan ini, karena memodifikasi dokumen asli akan memengaruhi "salinan" substring juga.
Juga, seperti yang disebutkan Donal, keuntungannya akan sangat terbebani oleh kerugiannya. Bayangkan Anda menulis sebuah program yang tergantung pada pustaka dan Anda menggunakan fungsi yang mengembalikan sebuah string. Bagaimana Anda bisa yakin bahwa nilai itu akan tetap konstan? Untuk memastikan tidak ada hal seperti itu terjadi, Anda harus selalu membuat salinan.
Bagaimana jika Anda memiliki dua utas yang berbagi string yang sama? Anda tidak ingin membaca string yang saat ini sedang ditulis ulang oleh utas lain, bukan? Oleh karena itu, string harus menjadi thread aman, yang menjadi kelas umum itu, akan membuat hampir setiap program Java yang jauh lebih lambat. Jika tidak, Anda harus membuat salinan untuk setiap utas yang memerlukan string itu atau Anda harus meletakkan kode menggunakan string itu dalam blok sinkronisasi, yang keduanya hanya memperlambat program Anda.
Untuk semua alasan ini, itu adalah salah satu keputusan awal yang dibuat untuk Jawa untuk membedakan dirinya dari C ++.
sumber
Alasan kekekalan string berasal dari konsistensi dengan tipe primitif lainnya dalam bahasa. Jika Anda memiliki nilai yang
int
berisi 42, dan Anda menambahkan nilai 1 ke dalamnya, Anda tidak mengubah nilai 42. Anda mendapatkan nilai baru, 43, yang sama sekali tidak terkait dengan nilai awal. Memotong primitif selain string tidak masuk akal secara konseptual; dan karena program-program yang memperlakukan string sebagai tidak berubah seringkali lebih mudah untuk dipikirkan dan dipahami.Selain itu, Java benar-benar menyediakan string yang bisa berubah dan tidak dapat diubah, seperti yang Anda lihat
StringBuilder
; sungguh, hanya defaultnya adalah string yang tidak dapat diubah. Jika Anda ingin memberikan referensi keStringBuilder
mana-mana, Anda dapat melakukannya. Java menggunakan tipe terpisah (String
danStringBuilder
) untuk konsep-konsep ini karena Java tidak memiliki dukungan untuk mengekspresikan kemampuan berubah-ubah atau ketiadaan dalam sistem tipenya. Dalam bahasa yang memiliki dukungan untuk kekekalan dalam sistem tipe mereka (mis. C ++const
), seringkali ada tipe string tunggal yang melayani kedua tujuan.Ya, menjadikan string tidak dapat diubah memungkinkan seseorang untuk mengimplementasikan beberapa optimasi khusus untuk string yang tidak dapat diubah, seperti interning, dan memungkinkan melewatkan referensi string di sekitar tanpa sinkronisasi di seluruh utas. Namun, ini membingungkan mekanisme dengan tujuan yang diinginkan dari bahasa dengan sistem tipe yang sederhana dan konsisten. Saya menyamakan ini dengan cara semua orang berpikir tentang pengumpulan sampah dengan cara yang salah; pengumpulan sampah bukanlah "reklamasi memori yang tidak digunakan"; itu adalah "mensimulasikan komputer dengan memori tidak terbatas" . Optimalisasi kinerja yang dibahas adalah hal-hal yang dilakukan untuk membuat tujuan string yang tidak dapat diubah bekerja dengan baik pada mesin nyata; bukan alasan untuk string seperti itu tidak berubah di tempat pertama.
sumber
43 = 6
dan mengharapkan angka 43 memiliki arti yang sama dengan angka 6.i
, bukan 42. Pertimbangkanstring s = "Hello "; s += "World";
. Anda mengubah nilai variabels
. Tapi string"Hello "
,"World"
dan"Hello World"
yang berubah.Kekekalan berarti bahwa konstanta yang dipegang oleh kelas yang tidak Anda miliki tidak dapat dimodifikasi. Kelas yang Anda tidak memiliki termasuk orang-orang yang berada di inti dari pelaksanaan Java, dan string yang tidak boleh diubah mencakup hal-hal seperti token keamanan, alamat layanan, dll Anda benar-benar tidak harus mampu memodifikasi orang-orang macam hal (dan ini berlaku dua kali lipat ketika beroperasi dalam mode kotak pasir).
Jika String tidak dapat diubah, setiap kali Anda mengambilnya dari beberapa konteks yang tidak ingin konten string berubah di bawah kakinya, Anda harus mengambil salinan “berjaga-jaga”. Itu jadi sangat mahal.
sumber
String
. Tapi, misalnya,Array
s bisa berubah-ubah. Jadi, mengapa tidakString
berubah danArray
tidak. Dan jika keabadian sangat penting, lalu mengapa Java membuatnya sangat sulit untuk dibuat dan bekerja dengan objek yang tidak dapat diubah?Bayangkan sebuah sistem di mana Anda menerima beberapa data, verifikasi kebenarannya dan kemudian meneruskannya (untuk disimpan dalam DB, misalnya).
Dengan asumsi bahwa data adalah a
String
dan panjangnya harus minimal 5 karakter. Metode Anda terlihat seperti ini:Sekarang kita dapat setuju, bahwa ketika
storeInDatabase
dipanggil di sini,input
akan sesuai dengan persyaratan. Tetapi jikaString
bisa berubah, maka pemanggil dapat mengubahinput
objek (dari utas lain) segera setelah itu diverifikasi dan sebelum itu disimpan dalam database . Ini membutuhkan pengaturan waktu yang baik dan mungkin tidak akan berjalan baik setiap saat, tetapi kadang-kadang, dia bisa membuat Anda menyimpan nilai yang tidak valid dalam database.Tipe data yang tidak dapat diubah adalah solusi yang sangat sederhana untuk masalah ini (dan banyak masalah terkait): setiap kali Anda memeriksa beberapa nilai, Anda dapat bergantung pada kenyataan bahwa kondisi yang diperiksa masih benar nantinya.
sumber
input
darihandle
metode ini sudah terlalu lama (tidak peduli apa yang asliinput
adalah), itu hanya akan membuang pengecualian. Anda sedang membuat input baru sebelum memanggil metode. Itu bukan masalah.Secara umum, Anda akan menemukan tipe nilai dan tipe referensi . Dengan tipe nilai, Anda tidak peduli dengan objek yang mewakilinya, Anda peduli nilai tersebut. Jika saya memberi Anda nilai, Anda berharap nilai itu tetap sama. Anda tidak ingin itu berubah tiba-tiba. Angka 5 adalah nilai. Anda tidak berharap itu berubah menjadi 6 tiba-tiba. String "Hello" adalah sebuah nilai. Anda tidak berharap itu berubah menjadi "P *** off" tiba-tiba.
Dengan jenis referensi Anda peduli tentang objek, dan Anda mengharapkannya berubah. Misalnya, Anda akan sering berharap array berubah. Jika saya memberi Anda sebuah array, dan Anda ingin menyimpannya persis seperti itu, Anda juga harus percaya kepada saya untuk tidak mengubahnya, atau Anda membuat salinannya.
Dengan kelas string Java, desainer harus membuat keputusan: Apakah lebih baik jika string berperilaku seperti tipe nilai, atau haruskah mereka berperilaku seperti tipe referensi? Dalam kasus string Java, keputusan dibuat bahwa mereka harus tipe nilai, yang berarti karena mereka adalah objek, mereka harus objek yang tidak dapat diubah.
Keputusan sebaliknya bisa dibuat, tetapi menurut saya akan menyebabkan banyak sakit kepala. Seperti yang dikatakan di tempat lain, banyak bahasa membuat keputusan yang sama dan sampai pada kesimpulan yang sama. Pengecualian adalah C ++, yang memiliki satu kelas string, dan string bisa konstan atau tidak konstan, tetapi dalam C ++, tidak seperti Java, parameter objek dapat dilewatkan sebagai nilai, dan bukan sebagai referensi.
sumber
Saya benar-benar terkejut tidak ada yang menunjukkan hal ini.
Jawaban: Itu tidak akan menguntungkan Anda secara signifikan, bahkan jika itu bisa berubah. Itu tidak akan menguntungkan Anda sebanyak yang menyebabkan masalah tambahan. Mari kita periksa dua kasus mutasi yang paling umum:
Mengubah satu karakter string
Karena setiap karakter dalam string Java membutuhkan 2 atau 4 byte, tanyakan pada diri sendiri, apakah Anda akan mendapatkan apa pun jika Anda dapat mengubah salinan yang ada?
Dalam skenario Anda mengganti karakter 2 byte dengan 4 byte satu (atau sebaliknya), Anda harus menggeser bagian string yang tersisa dengan 2 byte ke kiri atau ke kanan. Yang tidak jauh berbeda dari menyalin seluruh string sama sekali dari sudut pandang komputasi.
Ini juga merupakan perilaku yang sangat tidak teratur yang umumnya tidak diinginkan. Bayangkan seseorang menguji aplikasi dengan teks bahasa Inggris, dan ketika aplikasi tersebut diadopsi ke luar negeri, seperti Cina, semuanya mulai berjalan dengan aneh.
Menambahkan string (atau karakter) lain ke yang sudah ada
Jika Anda memiliki dua string arbitrer, mereka berada di dua lokasi memori yang berbeda. Jika Anda ingin mengubah yang pertama dengan menambahkan yang kedua, Anda tidak bisa hanya meminta memori tambahan di akhir string pertama, karena mungkin sudah terisi.
Anda harus menyalin string gabungan ke lokasi yang sama sekali baru, yang persis sama seolah-olah kedua string tidak dapat diubah.
Jika Anda ingin melakukan penambahan secara efisien, Anda mungkin ingin menggunakan
StringBuilder
, yang menyimpan jumlah ruang yang cukup besar di akhir string, hanya untuk tujuan kemungkinan penambahan di masa mendatang.sumber
mereka mahal dan menjaga mereka tetap memungkinkan untuk hal-hal seperti sub-string berbagi byte array dari string utama. (peningkatan kecepatan juga karena tidak perlu membuat array byte baru dan salin)
keamanan - tidak ingin paket Anda atau kode kelas dinamai ulang
[dihapus lama 3 memandang StringBuilder src - tidak berbagi memori dengan string (sampai dimodifikasi) Saya pikir itu dalam 1,3 atau 1,4]
cache kode hash
untuk string mutalble gunakan SB (builder atau buffer sesuai kebutuhan)
sumber
Strings seharusnya merupakan tipe data primitif di Jawa. Jika ya, maka string akan secara default dapat diubah dan kata kunci terakhir akan menghasilkan string yang tidak dapat diubah. String yang dapat diubah berguna dan ada beberapa hack untuk string yang dapat diubah dalam kelas stringbuffer, stringbuilder, dan charsequence.
sumber