Pro trénink hrátek s bajtkódem doporučuju následující soutěž:
http://zeroturnaround.com/blog/celebrate-the-programmers-day-with-zeroturnaround/
Tvrzení, že u dvou řetězcových konstant v téže třídě funguje == jako porovnání řetězců, bych rozšířil silnějším tvrzením: Ono to funguje, i když jsou třídy různé. Je to tak díky runtime constant poolu, které sdílí všechny třídy (AFAIK napříč classloadery). Udělal jsem takový malý experiment: http://ideone.com/snJNS
Trošku problém to může být u testů. Jednou (pri doučování Javy) jsem se setkal s kódem, který na první pohled fungoval korektně, ale jen díky tomu, že stringy pocházely z konstant. Teď přemýšlím, proč jsem nefunkčnost ukazoval tak krkolomně, když by mělo stačit new String("foo").
Na druhou stranu, nepamatuji si, že by se mi někdy povedlo takovou chybu udělat a podepřít si jí funkčním testem.
Ano mate pravdu. Ja jsem se o tom operatoru == popravde nechtel moc rozepisovat, protoze jeho pouziti nad stringy (=objekty) vede ke spatne odhalitelnym chybam (postacuje "pouze" nejakou metodu volat jako beanu na jinem pocitaci).
Implementace new String(String) vypada v OpenJDK takto:
public String(String original) { int size = original.count; char[] originalValue = original.value; char[] v; if (originalValue.length > size) { // The array representing the String is bigger than the new // String itself. Perhaps this constructor is being called // in order to trim the baggage, so make a copy of the array. int off = original.offset; v = Arrays.copyOfRange(originalValue, off, off+size); } else { // The array representing the String is the same // size as the String, so no point in making a copy. v = originalValue; } this.offset = 0; this.count = size; this.value = v; }
coz znamena dalsi zdroj potencialnich problemu v zavislosti na tom, jestli se vytvari novy objekt nebo ne.
Když použiju new, tak se nový objekt vytváří vždy, ne? Druhá věc je, jestli se kopíruje pole uvnitř řetězce, ale pokud porovnávám pomocí == dva Stringy, tak to nepoznám.
Speciální případ by teda byl, kdyby ten konstruktor si uložil vytvořený řetězec do nějakého poolu (třeba jen dočasně) a řetězec by pak byl znovupoužit při vytváření nové třídy. Ale to by byla hodně krkolomná implementace.
Napadá mě pro testovací účely napsat skript, který by každou řetězcovok konstantu obaloval výrazem new String(...). Dělal bych to úpravou bytecode. Myslím, že by to šlo takto: skript by našel všechna ldc (popř. ldc_w) odkazující na řetězec a udělal by s tím toto:
* před tuto instrukci by hodil dvojici instrukcí new java/lang/String a dup
* za tuto instrukci by hodil instrukci invokespecial java/lang/String.<init>(java/lang/String)V
S BCELem by to měla být otázka na chvilku, pokud člověk nenarazí na nějaký jeho bug. (Což na druhou stranu taky jde lehce, bohužel.) Mohlo by to být takové cvičení pro nováčky, kteří se chtějí v BCELu zorientovat. Skoky by už měl BCEL přečíslovat sám.
(Obdobně (ale možná i o něco jednodušeji) by šlo vyřešit Integery apod. Tam by pro zjednodušení stačilo nahradit volání továrny java/lang/Integer.valueOf(I)Ljava/lang/Integer; vlastní továrnou, která by vždy volala new Integer.)
Objekt se pres operator new() vytvari vzdy, to se neda obejit, jedine tak, ze by konstruktory byly zakazany a musely by se volat tovarni metody (coz se doporucuje treba u Integeru atd.)
No bugy v BCELu jsou to mate pravdu, ale zrovna pri zmene instrukci v bajtkodu me to fungovalo dobre. Kdyztak to resi (IMHO elegantnejsi) ASM :-)
K tomu new se chci zeptat - kdyz budu mit treba v cyklu new String("foo"), je tedy zaruceno, ze se pokazde vytvori novy objekt, nebo se muze stat ze behem optimalizaci se to hodi pred cyklus nebo uplne nahradi konstantou?
Jeste me napada treti moznost - ze je to nahrazeno pokud optimalizator dokaze, ze ten kod s adresou instance nijak nepracuje.
Sémanticky je tu new pro vytvoření nové instance a toto nesmí optimalizace porušit. Není nutné pokaždé vytvořit nový objekt v heapu, ale pak se musí JIT ujistit, že to nezmění sémantiku programu.
Pro představu:
* Oracle Java 7 umí v některých případech zjistit, že se vytváří defensivní kopie nějakého objektu, ale děje se tak naprosto zbytečně, tak se místo vytvoření kopie prostě předá původní objekt. Toto asi nebude zrovna triviální optimalizace, je nutné se například přesvědčit, že oba objekty nejsou porovnávány podle identity, jinak by to změnilo sémantiku.
* Údajně se taky někdy nové instance vytváří na zásobníku, když JVM dokáže zjistit samotný cyklus.
* Někde jsem taky četl o recyklaci výjimek a nevyplňování stack trace, když JVM zjistí, že to nezmění sémantiku. Možná to říkal Lukáš Křečan, už nevím.
Prostě JVM musí v takových případech dělat dokonalou potěmkinovu vesnici. Nemusí dělat přesně to, co tomu člověk řekne, ale musí ten "podvod" být uvěřitelný pro libovolného vnějšího pozorovatele, ať se v tom šťourá, jak chce.
JVM se muze chovat podobne jako Schrödingerova kocka :-)
Myslim tim to, ze pokud nezacnete explicitne zkoumat, jestli se skutecne vytvori novy objekt, tak se objekt nemusi vytvorit (ale samozrejme muze). Ale jakmile se ve vasem zdrojaku objevi neco, co zavisi na tom, ze se skutecne novy objekt vytvari (typicky pouziti ==), tak se skutecne objekt vytvori :-)
Vzhledem k tomu, ze Stringy jsou nemenitelne objekty (immutable), tak vetsinou neni nutne tvorit defenzivni kopii a zapis new String("foo") je ve vetsine pripadu (ne vzdy) jen neefektivnim zapisem "foo", protoze "foo" je instance tridy String, na ktere lze volat vsechny metody ("foo".toLowerCase() a tak dale).
Překvapuje mě že doposud nikdo nezmínil tenhle podraz který je přímo oficiálně dokumentovaný: http://docs.oracle.com/javase/6/docs/api/java/lang/String.html#intern()
Jo jo, na tohle jsme narazili nedavno. Predstavte si kod
class A {
public static final CNST = "Hello";
}
class B {
String foo() {
return A.CNST;
}
}
Clovek by cekal, ze kod metody foo() bude neco jako
GETSTATIC A.CNST
ARETURN
Nicmene javac vygeneruje
LDC "Hello"
ARETURN
Tohle me stalo mnoho veceru nez jsem odladil...fix ve VM byl trivialni.
S těmi int/Integery mě to připomělo chybu v jedné aplikaci, kdy se při refaktoringu nahradily některé inty za Integery, tj. primitivní datový typ za objektový typ. Díky autoboxingu to skoro všude pracovalo dobře, ale v jedné metodě se používal operátor ==, který funguje pouze ve třech případech ze čtyř:
int == int ok
int == Integer ok
Integer == int ok
Integer == Integer dela to, co je napsano, tedy nikoli to, co chtel programator :-)
ano, to je jasne, ale docela dost lidi v tom rozlisovani dela chybu (je to videt na testech, popr. staci si pustit FindBugs na skoro kazdem vetsim Javovskem projektu). Urcita zakernost operatoru == nad retezci je v tom, ze nekdy to funguje (kdyz se porovnavaji retezce z constant poolu) a nekdy ne (kdyz se retezce ziskavaji jinde).
A pokud je situace takova, ze je treba cast aplikace testovana na "statickych datech" a projde testy a v praxi se namisto "statickych dat" neco nacita ze souboru ci databaze, je o problem postarano.
Nojo, to si do CVcka prece napise kazdy, kdo si projde zakladnim kurzem Javy nekde na skole, kde se takove "podruznosti" jako je rozdil mezi == a .equals() pri praci s objekty ani neprobiraji.
Ono se staci zeptat i na nejake zakladni veci ohledne kolekci napriklad, nemluvim o propojeni Javy a relacni DB, kde se taky dozvite spoustu zajimavosti a novinek z oboru relacnich databazi :-)
Tiez sa pridavam k dakovaniu, velmi prijemny clanok, tato nizsia uroven je celkom prijemne osviezenie popri rieseni problemov z praxe, ze ktoryze to superframework je najmensie zlo :)
a myslim ze filozofovanie nad bytecode je velmi dobry namet na niektoru z nasledujucich vecernych univerzit u nas vo firme