Zašto su Java Stringovi nepromenljivi?
String je verovatno najčešće korišćeni referentni tip u Javi, pa su dizajneri klase String očigledno uložili mnogo truda u nju.
Uzmimo za primer nepromenljivost (imutabilnost) Stringa.
- Klasa String je modifikovana ključnom rečju final, tako da nema potklase, što znači da nijedna potklasa ne može da prepiše njene metode i promeni njeno ponašanje.
- Podaci klase String se čuvaju u nizu
char[], a taj niz je takođe modifikovan ključnom rečju final, što znači da String objekat ne može da se izmeni — jednom kada se inicijalizuje, vrednost je fiksna.
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
}Chenmo Wang Er"; String password = "123456"; User user = new User(username, password);
// dobavlja korisničke akreditive
String[] credentials = getUserCredentials(user);
// pokušava da izmeni korisničko ime i lozinku iz niza koji vratio getUserCredentials
credentials[0] = "Chen Qingyang";
credentials[1] = "612311";
// ispisuje originalno korisničko ime i lozinku iz User objekta
System.out.println("Originalno korisničko ime: " + user.getUsername());
System.out.println("Originalna lozinka: " + user.getPassword());
}
public static String[] getUserCredentials(User user) {
String[] credentials = new String[2];
credentials[0] = user.getUsername();
credentials[1] = user.getPassword();
return credentials;
}
}
U ovom primeru, iako pokušavamo da izmenimo niz stringova (korisničko ime i lozinku) koji je vratio getUserCredentials, korisničko ime i lozinka u originalnom User objektu ostaju nepromenjeni. Ovo dokazuje da nepromenljivost stringova pomaže u zaštiti bezbednosti String objekata.
Drugo, garantuje se da se heš vrednost neće često menjati. Ipak, String se često koristi kao ključ u [heš tabelama](https://javasi.dev/collection/hashmap.html), a da se vrednost često menja, performanse heš tabele bile bi vrlo loše.
U klasi String, heš vrednost se kešira pri prvom izračunavanju, a svaki naredni zahtev za tu heš vrednost će direktno koristiti keširanu vrednost. Ovo pomaže u poboljšanju performansi struktura podataka poput heš tabela. Evo jednostavnog primera koji prikazuje mehanizam keširanja heš vrednosti stringova:
```java
String text1 = "Chenmo Wang Er";
String text2 = "Chenmo Wang Er";
// izračunava heš vrednost stringa text1 — u ovom trenutku se vrednost računa i kešira
int hashCode1 = text1.hashCode();
System.out.println("Prvo izračunavanje heš vrednosti text1: " + hashCode1);
// ponovo izračunava heš vrednost stringa text1 — ovaj put direktno vraća keširanu vrednost
int hashCode1Cached = text1.hashCode();
System.out.println("Drugo izračunavanje: " + hashCode1Cached);
// izračunava heš vrednost stringa text2 — zbog postojanja bazena string konstanti,
// text1 i text2 zapravo pokazuju na isti string objekat,
// pa se ovde direktno vraća keširana heš vrednost
int hashCode2 = text2.hashCode();
System.out.println("text2 direktno koristi keš: " + hashCode2);U ovom primeru kreiramo dva stringa sa istim sadržajem, text1 i text2. Prilikom prvog izračunavanja heš vrednosti text1, vrši se stvarni izračun i ta vrednost se kešira. Kada ponovo izračunavamo heš vrednost text1 ili heš vrednost text2 (koji ima isti sadržaj), direktno se vraća keširana vrednost, bez ponovnog računanja.
Pošto je String objekat nepromenljiv, njegova heš vrednost se ne menja nakon kreiranja. To omogućava klasi String da kešira heš vrednost i poboljša performanse struktura podataka poput heš tabela. Da je String promenljiv, pri svakoj izmeni bi heš vrednost morala ponovo da se izračuna, što bi smanjilo performanse.
Treće, ovo omogućava implementaciju bazena string konstanti — Java čuva stringove istog sadržaja u bazenu string konstanti. Na taj način, string promenljive sa istim sadržajem mogu da pokazuju na isti String objekat, čime se štedi memorijski prostor.
„Zbog nepromenljivosti stringova, implementacija nekih metoda klase String na kraju uvek vraća novi string objekat.” Pošto je Sanmei malo razmislila, nastavio sam.
„Uzmimo za primer metod substring().”
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}Metod substring() služi za izdvajanje podstringa i na kraju uvek vraća novi string objekat dobijen preko new.
„Ista stvar je i sa metodom concat().”
public String concat(String str) {
int olen = str.length();
if (olen == 0) {
return this;
}
if (coder() == str.coder()) {
byte[] val = this.value;
byte[] oval = str.value;
int len = val.length + oval.length;
byte[] buf = Arrays.copyOf(val, len);
System.arraycopy(oval, 0, buf, val.length, oval.length);
return new String(buf, coder);
}
int len = length();
byte[] buf = StringUTF16.newBytesFor(len + olen);
getBytes(buf, 0, UTF16);
str.getBytes(buf, len, UTF16);
return new String(buf, UTF16);
}Metod concat() služi za nadovezivanje stringova i, bez obzira na to da li se kodiranje poklapa, na kraju takođe vraća novi string objekat.
„I metod replace() za zamenu radi na isti način, Sanmej — možeš sama kasnije da pogledaš izvorni kod, i on vraća novi string objekat.”
„To znači da, bilo da je u pitanju izdvajanje, nadovezivanje ili zamena, operacija se ne vrši nad originalnim stringom, već se uvek kreira novi string objekat. Drugim rečima, nakon što se ove operacije izvrše, originalni string objekat se nije promenio.”
„Zapamti, Sanmej — jednom kada se String objekat kreira, on ostaje fiksan; svaka izmena String objekta neće uticati na originalni string objekat, već će uvek generisati novi string objekat.”
„Dobro, zapamtila sam, Erge.” Sanmej je bila poslušna.
„Da onda zaustavimo se ovde za danas, a kasnije ćemo detaljnije razraditi svaku oblast. Možeš da potražiš neke materijale i da prvo ponoviš, a ja ću izaći da malo prošetam i odmorim glavu...”
