Mengapa String tidak dapat diubah di Jawa?

78

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.

yfklon
sumber
20
Secara teknis, ini bukan duplikat, tetapi Eric Lippert memberikan jawaban yang bagus untuk pertanyaan ini di sini: programmers.stackexchange.com/a/190913/33843
Heinzi

Jawaban:

105

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 substringmetode yang dilakukan (tidak harus dilakukan - juga memperkenalkan kemungkinan beberapa kebocoran memori juga).

Jika kamu melakukan:

String foo = "smiles";
String bar = foo.substring(1,5);

Nilai baradalah 'mil'. Namun, keduanya foodan bardapat 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.

foo | | (0, 6)
    ay
    tersenyum
     ^ ^
bar | | (1, 5)

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)

   public String(char value[], int offset, int count) {
       if (offset < 0) {
           throw new StringIndexOutOfBoundsException(offset);
       }
       if (count < 0) {
           throw new StringIndexOutOfBoundsException(count);
       }
       // Note: offset or count might be near -1>>>1.
       if (offset > value.length - count) {
           throw new StringIndexOutOfBoundsException(offset + count);
       }
       this.offset = 0;
       this.count = count;
       this.value = Arrays.copyOfRange(value, offset, offset+count);
   }

   // Package private constructor which shares value array for speed.
   String(int offset, int count, char value[]) {
       this.value = value;
       this.offset = offset;
       this.count = count;
   }

   public String substring(int beginIndex, int endIndex) {
       if (beginIndex < 0) {
           throw new StringIndexOutOfBoundsException(beginIndex);
       }
       if (endIndex > count) {
           throw new StringIndexOutOfBoundsException(endIndex);
       }
       if (beginIndex > endIndex) {
           throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
       }
       return ((beginIndex == 0) && (endIndex == count)) ? this :
           new String(offset + beginIndex, endIndex - beginIndex, value);
   }

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 Stringtidak 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).

Banyak aplikasi Java skala besar saat ini mengalami hambatan memori. Pengukuran telah menunjukkan bahwa sekitar 25% dari Java heap live set data dalam jenis aplikasi ini dikonsumsi oleh objek String. Lebih jauh, kira-kira setengah dari objek String tersebut adalah duplikat, di mana duplikat berarti string1.equals (string2) benar. Memiliki duplikat objek String pada heap, pada dasarnya, hanya membuang-buang memori. ...

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 NSStringdan NSMutableString.

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
3
Java menyediakan string yang bisa berubah dan tidak berubah. Jawaban ini merinci beberapa keunggulan kinerja yang dapat dilakukan pada string yang tidak dapat diubah, dan beberapa alasan seseorang mungkin memilih data yang tidak dapat diubah; tetapi tidak membahas mengapa versi yang tidak dapat diubah adalah versi default.
Billy ONeal
3
@BillyONeal: default yang aman dan alternatif yang tidak aman hampir selalu mengarah ke sistem yang lebih aman daripada pendekatan yang berlawanan.
Joachim Sauer
4
@ BillyONeal Jika yang tidak bisa diubah bukan default masalah konkurensi, keamanan, dan hash akan lebih umum. Perancang bahasa telah memilih (sebagian sebagai tanggapan terhadap C) untuk membuat bahasa di mana pengaturan default diatur untuk mencoba mencegah sejumlah bug umum untuk mencoba meningkatkan efisiensi programmer (tidak perlu khawatir tentang bug ini lagi). Ada lebih sedikit bug (jelas dan tersembunyi) dengan string tidak berubah dari pada yang bisa berubah.
@ Joachim: Saya tidak mengklaim sebaliknya.
Billy ONeal
1
Secara teknis, Common Lisp memiliki string yang dapat berubah-ubah, untuk operasi dan simbol "seperti string" dengan nama yang tidak dapat diubah untuk pengidentifikasi yang tidak dapat diubah.
Vatine
21

Alasan yang saya ingat:

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

  2. 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.

  3. Immutability memungkinkan String untuk me-cache hashcode-nya.

  4. Menjadikannya aman-utas.

ORANG BODOH
sumber
7

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:

String s1 = "Java";
String s2 = "Java";

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.

saidesh kilaru
sumber
5

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 ++.

Neil
sumber
Secara teoritis, Anda dapat melakukan manajemen buffer multi-layer yang memungkinkan copy-on-mutation-if-shared, tetapi itu sangat sulit untuk membuat pekerjaan secara efisien dalam lingkungan multi-threaded.
Donal Fellows
@ DonalFellows Saya hanya berasumsi bahwa sejak Java Virtual Machine tidak ditulis dalam Java (jelas), dikelola secara internal menggunakan pointer bersama atau semacamnya.
Neil
5

Alasan kekekalan string berasal dari konsistensi dengan tipe primitif lainnya dalam bahasa. Jika Anda memiliki nilai yang intberisi 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 ke StringBuildermana-mana, Anda dapat melakukannya. Java menggunakan tipe terpisah ( Stringdan StringBuilder) 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.

Billy ONeal
sumber
@ Billy-Oneal .. Mengenai "Jika Anda memiliki int yang berisi nilai 42, dan Anda menambahkan nilai 1 ke dalamnya, Anda tidak mengubah 42. Anda mendapatkan nilai baru, 43, yang sama sekali tidak terkait dengan awal nilai. " Apa kamu yakin akan hal itu?
Shamit Verma
@Shamit: Ya, saya yakin. Menambahkan 1 hingga 42 menghasilkan 43. Itu tidak membuat angka 42 memiliki arti yang sama dengan angka 43.
Billy ONeal
@Shamit: Sama halnya, Anda tidak dapat melakukan sesuatu seperti 43 = 6dan mengharapkan angka 43 memiliki arti yang sama dengan angka 6.
Billy ONeal
int i = 42; i = i +1; kode ini akan menyimpan 42 dalam memori dan kemudian mengubah nilai di lokasi yang sama menjadi 43. Jadi, variabel "i" memperoleh nilai baru 43.
Shamit Verma
@Shamit: Dalam hal ini, Anda bermutasi i, bukan 42. Pertimbangkan string s = "Hello "; s += "World";. Anda mengubah nilai variabel s. Tapi string "Hello ", "World"dan "Hello World"yang berubah.
Billy ONeal
4

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.

Donal Fellows
sumber
4
Argumen yang persis sama ini berlaku untuk semua jenis, bukan hanya untuk String. Tapi, misalnya, Arrays bisa berubah-ubah. Jadi, mengapa tidak Stringberubah dan Arraytidak. Dan jika keabadian sangat penting, lalu mengapa Java membuatnya sangat sulit untuk dibuat dan bekerja dengan objek yang tidak dapat diubah?
Jörg W Mittag
1
@ JörgWMittag: Saya berasumsi pada dasarnya itu adalah pertanyaan tentang seberapa radikal mereka ingin menjadi. Memiliki String yang tidak dapat diubah cukup radikal, kembali ke Java 1.0 hari. Memiliki kerangka koleksi (terutama atau bahkan eksklusif) yang tidak dapat diubah juga, bisa saja terlalu radikal untuk mendapatkan penggunaan bahasa yang luas.
Joachim Sauer
Melakukan kerangka koleksi yang kekal cukup sulit untuk membuat performan, berbicara sebagai seseorang yang telah menulis hal seperti itu (tetapi tidak di Jawa). Saya juga berharap sepenuhnya bahwa saya memiliki array yang kekal; itu akan menyelamatkan saya sedikit kerja.
Donal Fellows
@ DonalFellows: pcollections bertujuan melakukan hal itu (tidak pernah menggunakannya sendiri).
Joachim Sauer
3
@ JörgWMittag: Ada orang-orang (umumnya dari sudut pandang yang murni fungsional) yang berpendapat bahwa semua tipe harus tidak berubah. Demikian juga, saya berpikir bahwa jika Anda menambahkan semua masalah yang satu berurusan dengan bekerja dengan keadaan berubah-ubah dalam perangkat lunak paralel dan bersamaan, Anda mungkin setuju bahwa bekerja dengan objek yang tidak dapat berubah seringkali jauh lebih mudah daripada yang bisa diubah.
Steven Evers
2

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 Stringdan panjangnya harus minimal 5 karakter. Metode Anda terlihat seperti ini:

public void handle(String input) {
  if (input.length() < 5) {
    throw new IllegalArgumentException();
  }
  storeInDatabase(input);
}

Sekarang kita dapat setuju, bahwa ketika storeInDatabasedipanggil di sini, inputakan sesuai dengan persyaratan. Tetapi jika Stringbisa berubah, maka pemanggil dapat mengubah inputobjek (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.

Joachim Sauer
sumber
Terima kasih untuk penjelasannya. Bagaimana jika saya memanggil metode pegangan seperti ini; handle (String baru (input + "naberlan")). Saya kira saya dapat menyimpan nilai yang tidak valid di db seperti ini.
yfklon
1
@blank: baik, karena inputdari handlemetode ini sudah terlalu lama (tidak peduli apa yang asli input adalah), itu hanya akan membuang pengecualian. Anda sedang membuat input baru sebelum memanggil metode. Itu bukan masalah.
Joachim Sauer
0

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.

gnasher729
sumber
0

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.

Rok Kralj
sumber
-2
  1. 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)

  2. 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]

  3. cache kode hash

  4. untuk string mutalble gunakan SB (builder atau buffer sesuai kebutuhan)

tgkprog
sumber
2
1. Tentu saja, ada hukuman karena tidak dapat menghancurkan bagian string yang lebih besar jika ini terjadi. Magang tidak gratis; meskipun itu meningkatkan kinerja untuk banyak program dunia nyata. 2. Mungkin ada "string" dan "ImmutableString" yang dapat memenuhi persyaratan itu. 3. Saya tidak yakin saya mengerti bahwa ...
Billy ONeal
.3. seharusnya cache kode hash. Ini juga bisa dilakukan dengan string yang bisa berubah. @ billy-oneal
tgkprog
-4

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.

CWallach
sumber
3
Ini tidak menjawab aspek "mengapa" dari apa yang sekarang ditanyakan. Selain itu, java final tidak berfungsi seperti itu. String yang dapat dimutakhirkan bukanlah hacks, melainkan pertimbangan desain nyata berdasarkan penggunaan string yang paling umum dan optimisasi yang dapat dilakukan untuk meningkatkan jvm.
1
Jawaban atas "mengapa" adalah keputusan desain bahasa yang buruk. Tiga cara yang sedikit berbeda untuk mendukung string yang bisa diubah adalah hack yang harus ditangani oleh compiler / JVM.
CWallach
3
String dan StringBuffer adalah yang asli. StringBuilder ditambahkan kemudian mengakui kesulitan desain dengan StringBuffer. String yang dapat berubah dan tidak berubah menjadi objek yang berbeda ditemukan dalam banyak bahasa karena pertimbangan desain dibuat berulang-ulang dan memutuskan bahwa masing-masing adalah objek yang berbeda setiap kali. C # "String tidak bisa diubah" dan Mengapa .NET String tidak dapat diubah? , objektif C NSString tidak dapat diubah sedangkan NSMutableString tidak dapat berubah. stackoverflow.com/questions/9544182