Detaljno objašnjenje metoda String.intern()
"Erge, pročitala sam onaj članak koji si mi poslao i posle njega više nemam volje da učim Javu!" rekla je Sanmej srditno.
"Koji članak?" videvši da Sanmej deluje potišteno, zabrinuto sam upitao.
"Pa onaj u kom tehnički tim Meituana detaljno analizira String.intern()!" odgovorila je Sanmej.
"A, da, sećam se. Pa dobar je to članak — temeljan, a pristupačan, pravi vrhunac među člancima. Posle njega bi trebalo da ti intern metod klase String bude potpuno jasan."
"Dobar je, ali jednostavno ga ne razumem!" rekla je Sanmej uvređeno. "Erge, možeš li mi ti lično objasniti?"
"Dobro, a bazen string konstanti o kome smo učili prošli put — da li ti je sve jasno?"
"Jeste," Sanmej je blago klimnula glavom.
Da biste razumeli ovaj članak tehničkog tima Meituana, dovoljno je da zapamtite nekoliko ključnih stvari:
Prvo, string objekti deklarisani pod navodnicima biće sačuvani u bazenu string konstanti.
Drugo, string objekat kreiran ključnom reči new najpre se traži u bazenu string konstanti: ako se ne pronađe, kreira se jedan takav u bazenu, a zatim se string objekat kreira i na hipu; ako se pronađe, string objekat se kreira direktno samo na hipu.
Treće, za string objekte koji nisu deklarisani pod navodnicima — kao što je s1 u kodu ispod:
String s1 = new String("Erge") + new String("Sanmei");Ako želite i sadržaj promenljive s1 da smestite u bazen string konstanti, to možete postići pozivom metoda intern().
Treba imati na umu da je u verziji Java 7 bazen string konstanti premešten iz permanentne generacije (PermGen) na hip, iako u tom trenutku permanentna generacija još uvek nije potpuno uklonjena. U verziji Java 8 permanentna generacija je konačno i potpuno uklonjena.
Ova promena je direktno uticala i na ponašanje metoda String.intern() prilikom izvršavanja. Pre verzije Java 7, prilikom poziva String.intern(), bez obzira na to da li je objekat već kreiran na hipu, u bazenu string konstanti bi se svakako kreirao novi objekat sa potpuno istim sadržajem. Od verzije Java 7 nadalje, pošto se bazen string konstanti nalazi na hipu, prilikom poziva String.intern(), ako je objekat već kreiran na hipu, u bazenu string konstanti više nije potrebno kreirati novi objekat — umesto toga se čuva direktna referenca na objekat sa hipa, čime se štedi deo memorijskog prostora.
"Još uvek mi nije sasvim jasno, Erge," Sanmej je bila vidno ogorčena.
"Hm... ne plaši se, Sanmej. Hajde prvo da pokušaš da pogodiš rezultat sledećeg koda," rekao sam.
String s1 = new String("Erge Sanmei");
String s2 = s1.intern();
System.out.println(s1 == s2);"Erge, za ovo baš nemam pojma, bolje da mi ti direktno objasniš," rekla je Sanmej.
"Dobro."
U prvoj liniji koda, u bazenu string konstanti se prvo kreira objekat "Erge Sanmej", a zatim se na hipu kreira još jedan objekat "Erge Sanmej"; s1 referencira objekat na hipu.
U drugoj liniji koda, nad s1 se poziva metod intern(). Ovaj metod u bazenu string konstanti traži da li postoji string "Erge Sanmej" — u ovom trenutku on tamo postoji, pa s2 referencira objekat iz bazena string konstanti.
To znači da su referentne adrese promenljivih s1 i s2 različite — jedna dolazi sa hipa, a druga iz bazena string konstanti — pa je ispisani rezultat false.
"Hajde da pogledamo rezultat izvršavanja," rekao sam.
false"Nacrtaću ti jednu skicu da bi ti lakše bilo da shvatiš," rekao sam strpljivo, videvši iznenađen izraz na njenom licu.

"Sad ti je jasno?" upitao sam Sanmej.
"Jeste, sve mi je odjednom postalo kristalno jasno!" rekla je Sanmej.
"Dobro, hajde da pogledamo i sledeći kod."
String s1 = new String("Erge") + new String("Sanmei");
String s2 = s1.intern();
System.out.println(s1 == s2);"Zar i ovde ispisuje false?" Sanmej je bila pomalo nesigurna.
"Ne, ovaj kod će ispisati true," demantovao sam njenu pretpostavku.
"Zašto?" Sanmej je nestrpljivo želela da sazna odgovor.
U prvoj liniji koda, u bazenu string konstanti kreira se jedan objekat "Erge" i jedan objekat "Sanmej"; zatim se na hipu kreiraju dva anonimna objekta, "Erge" i "Sanmej", i konačno još jedan objekat "Erge Sanmej" (objasnićemo uskoro). s1 referencira objekat "Erge Sanmej" koji se nalazi na hipu.
U drugoj liniji koda, nad s1 se poziva metod intern(). Ovaj metod u bazenu string konstanti traži da li postoji objekat "Erge Sanmej" — u ovom trenutku ga nema, ali zato postoji na hipu, pa bazen string konstanti čuva referencu na taj "Erge Sanmej" objekat sa hipa. Drugim rečima, s2 i s1 imaju istu referentnu adresu, pa je ispisani rezultat true.
"Hajde da pogledamo rezultat izvršavanja," rekao sam pun samopouzdanja.
true"Nacrtaću ti još jednu skicu, da bi ti bilo lakše da razumeš."

"O, sad mi je jasno!" Sanmej je dahnula s olakšanjem, s utiskom da intern zapravo nije ništa teško za razumevanje. "Ali imam jedno pitanje — kada je zapravo kreiran objekat 'Erge Sanmej'?"
"Bravo, Sanmej, uhvatila si srž problema. Hajde da objasnimo red String s1 = new String("Erge") + new String("Sanmei")," rekao sam veoma zadovoljan njenim napretkom.
- Kreira se string objekat "Erge" koji se smešta u bazen string konstanti.
- Kreira se string objekat "Sanmej" koji se smešta u bazen string konstanti.
- Izvršava se
new String("Erge")— na hipu se kreira string objekat čiji je sadržaj "Erge". - Izvršava se
new String("Sanmei")— na hipu se kreira string objekat čiji je sadržaj "Sanmei". - Izvršava se
new String("Erge") + new String("Sanmei")— kreira se jedan StringBuilder objekat, u njega se dodaju "Erge" i "Sanmei", a zatim se poziva toString() metod nad StringBuilder objektom, čime nastaje novi string objekat čiji je sadržaj "Erge Sanmei". Ovaj novi string objekat se čuva na hipu.
Drugim rečima, kada kompajler naiđe na operator +, red new String("Erge") + new String("Sanmei") se kompajlira u sledeći kod:
new StringBuilder().append("Erge").append("Sanmei").toString();Stvarni proces izvršavanja je sledeći:
- Kreira se jedan StringBuilder objekat.
- Nad StringBuilder objektom se poziva append("Erge"), čime se "Erge" dodaje u StringBuilder.
- Nad StringBuilder objektom se poziva append(), čime se dodaje u StringBuilder.
- Nad StringBuilder objektom se poziva metod toString(), čime se StringBuilder pretvara u novi string objekat čiji je sadržaj .
O StringBuilderu će kasnije biti reči detaljno. Za sada, toliko.
Treba obratiti pažnju na to da, iako intern može da obezbedi da svi stringovi sa istim sadržajem dele isti memorijski prostor, ne treba ga zloupotrebljavati — svaki keš-bazen ima ograničenu veličinu, pa ne možemo bezrazložno zauzeti inače oskudni keš-prostor i time sprečiti druge stringove da zauzmu svoje mesto.
Pored toga, bazen string konstanti je u suštini StringTable fiksne veličine. Ukoliko se u njega smesti previše stringova, nastupiće ozbiljne kolizije heš-vrednosti, što će dovesti do izduživanja povezanih listâ. Duže povezane liste znače i drastičan pad performansi bazena string konstanti, jer je za pretragu potrebno proći kroz njih element po element, a to košta vremena.
"U redu, Sanmej, o intern metodu klase String toliko za sada. Sad ti je jasno?" upitao sam.
"Erge, baš si odličan!"
Gledajući kako Sanmej korak po korak napreduje, i ja sam osetio iskreno zadovoljstvo.
