Detaljno objašnjenje metoda String.intern()
Pretpostavimo da ste pročitali onaj članak koji tehnički tim Meituana detaljno analizira String.intern(), ali vam on i dalje nije jasan.
To je vrhunski članak — temeljan, a pristupačan, pravi vrhunac među člancima. Posle njega bi trebalo da vam intern metod klase String bude potpuno jasan. Ali hajde da ga ipak detaljno objasnimo.
Da li vam je bazen string konstanti, o kome smo učili prošli put, jasan?
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("Autor") + new String("Marko");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.
Ako vam i dalje nije sasvim jasno, hajde da pokušamo da pogodimo rezultat sledećeg koda:
String s1 = new String("Autor Marko");
String s2 = s1.intern();
System.out.println(s1 == s2);Najbolje da ga direktno objasnimo.
U prvoj liniji koda, u bazenu string konstanti se prvo kreira objekat "Autor Marko", a zatim se na hipu kreira još jedan objekat "Autor Marko"; 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 "Autor Marko" — 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:
falseEvo i jedne skice da bi bilo lakše da se shvati:

Sad je jasnije? Hajde da pogledamo i sledeći kod.
String s1 = new String("Autor") + new String("Marko");
String s2 = s1.intern();
System.out.println(s1 == s2);Možda bi neko pomislio da i ovde ispisuje false, ali ovaj kod će ispisati true.
Zašto?
U prvoj liniji koda, u bazenu string konstanti kreira se jedan objekat "Autor" i jedan objekat "Marko"; zatim se na hipu kreiraju dva anonimna objekta, "Autor" i "Marko", i konačno još jedan objekat "Autor Marko" (objasnićemo uskoro). s1 referencira objekat "Autor Marko" 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 "Autor Marko" — u ovom trenutku ga nema, ali zato postoji na hipu, pa bazen string konstanti čuva referencu na taj "Autor Marko" objekat sa hipa. Drugim rečima, s2 i s1 imaju istu referentnu adresu, pa je ispisani rezultat true.
Hajde da pogledamo rezultat izvršavanja:
trueEvo još jedne skice, da bi bilo lakše da se razume.

I sad se nameće pitanje — kada je zapravo kreiran objekat "Autor Marko"?
Hajde da objasnimo red String s1 = new String("Autor") + new String("Marko").
- Kreira se string objekat "Autor" koji se smešta u bazen string konstanti.
- Kreira se string objekat "Marko" koji se smešta u bazen string konstanti.
- Izvršava se
new String("Autor")— na hipu se kreira string objekat čiji je sadržaj "Autor". - Izvršava se
new String("Marko")— na hipu se kreira string objekat čiji je sadržaj "Marko". - Izvršava se
new String("Autor") + new String("Marko")— kreira se jedan StringBuilder objekat, u njega se dodaju "Autor" i "Marko", a zatim se poziva toString() metod nad StringBuilder objektom, čime nastaje novi string objekat čiji je sadržaj "Autor Marko". Ovaj novi string objekat se čuva na hipu.
Drugim rečima, kada kompajler naiđe na operator +, red new String("Autor") + new String("Marko") se kompajlira u sledeći kod:
new StringBuilder().append("Autor").append("Marko").toString();Stvarni proces izvršavanja je sledeći:
- Kreira se jedan StringBuilder objekat.
- Nad StringBuilder objektom se poziva append("Autor"), čime se "Autor" 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.
To je toliko o intern metodu klase String za sada. Nadam se da je sad sve jasnije.
