Dobrodošli v drugem delu Pod pokrovom. Ta stolpec daje razvijalcem Java vpogled v skrivnostne mehanizme, ki kliknejo in se vrtijo pod njihovimi programi Java. V tem mesecu se nadaljuje razprava o naboru bajt kod navideznega stroja Java (JVM). Njegov poudarek je na načinu ravnanja JVM končno
in bajt kod, ki so pomembni za te klavzule.
Končno: Nekaj za razveseljevanje
Ko Java navidezni stroj izvrši bajtode, ki predstavljajo program Java, lahko na en od več načinov zapusti blok kode - stavke med dvema ujemajočima se zavitima oklepajema. Prvič, JVM bi lahko preprosto izvedel mimo zapiralne oklepaje bloka kode. Lahko pa naleti na izjavo o prekinitvi, nadaljevanju ali vrnitvi, zaradi katere skoči iz bloka kode od nekje na sredini bloka. Končno bi lahko vrgli izjemo, zaradi katere JVM skoči na ujemajočo se ulovno klavzulo ali, če ni ujemajoče se ulovne klavzule, prekine nit. S temi potencialnimi izhodnimi točkami, ki obstajajo znotraj enega bloka kode, je zaželeno, da na preprost način izrazimo, da se je nekaj zgodilo, ne glede na to, kako izstopimo iz bloka kode. V Javi je taka želja izražena z poskusite končno
klavzulo.
Za uporabo a poskusite končno
klavzula:
priloži v a
poskusite
blokira kodo, ki ima več izhodnih točk, indal v
končno
blokira kodo, ki se mora zgoditi ne glede na to, kakoposkusite
blok je zaprt.
Na primer:
poskusite {// blok kode z več izhodnimi točkami} končno {// blok kode, ki se vedno izvrši, ko je izhodni blok izpuščen, // ne glede na to, kako je izhodni blok izpuščen}
Če jih imate ulov
klavzule, povezane z poskusite
blok, morate postaviti končno
klavzula po vseh ulov
klavzule, kot v:
poskusite {// blok kode z več izstopnimi točkami} catch (Cold e) {System.out.println ("Prestrašen!"); } catch (APopFly e) {System.out.println ("Ujeti pop muho!"); } catch (SomeonesEye e) {System.out.println ("Ujel nekoga!"); } končno {// Blok kode, ki se vedno izvede, ko izstopi blok poskus, // ne glede na to, kako blok poskusa izstopi. System.out.println ("Ali je zaradi tega treba navijati?"); }
Če med izvajanjem kode v poskusite
blok, vrže se izjema, ki jo obravnava ulov
klavzula, povezana z poskusite
blok, končno
klavzula bo izvršena po ulov
klavzulo. Na primer, če a Hladno
izjema se vrže med izvajanjem stavkov (ni prikazano) v poskusite
v zgornjem bloku bi se v standardni izhod zapisalo naslednje besedilo:
Prehlajeni! Je to nekaj za razveseljevanje?
Klavzule "poskusi končno" v bajtkodah
V bajtkodah končno
klavzule delujejo kot miniaturne podprograme znotraj metode. Na vsaki izstopni točki znotraj a poskusite
blok in z njim povezani ulov
klavzule, miniaturna podprogram, ki ustreza končno
se imenuje klavzula. Po končno
klavzula dokonča - dokler se dokonča z izvajanjem preteklega zadnjega stavka v končno
, ne z vrnitvijo izjeme ali izvedbo vrnitve, nadaljevanja ali preloma - miniaturna podprogram se vrne sam. Izvedba se nadaljuje tik pred točko, kjer je bila najprej imenovana miniaturna podprogram, torej poskusite
blok lahko zapustite na ustrezen način.
Opcode, zaradi katerega JVM preskoči na miniaturno podprogram, je jsr navodila. The jsr navodilo sprejme dvobajtni operand, odmik od lokacije jsr navodila, kje se začne miniaturna podprogram. Druga različica jsr navodilo je jsr_w, ki opravlja enako funkcijo kot jsr vendar zavzame širok (štiribajtni) operand. Ko JVM naleti na a jsr ali jsr_w navodilo potisne povratni naslov na sklad, nato pa nadaljuje izvajanje na začetku miniaturne podprograma. Vrnjeni naslov je odmik bajtke takoj za jsr ali jsr_w navodila in njegovi operandi.
Ko se miniaturna podprogram zaključi, prikliče ponovitev navodilo, ki se vrne iz podprograma. The ponovitev navodilo sprejme en operand, indeks lokalnih spremenljivk, kjer je shranjen povratni naslov. Opcode, ki obravnavajo končno
klavzule so povzete v naslednji tabeli:
Opcode | Operand (i) | Opis |
---|---|---|
jsr | branchbyte1, branchbyte2 | potisne povratni naslov, veje pobotajo |
jsr_w | branchbyte1, branchbyte2, branchbyte3, branchbyte4 | potisne povratni naslov, razveja se v velik odmik |
ponovitev | indeks | vrne se na naslov, shranjen v indeksu lokalne spremenljivke |
Ne mešajte miniaturne podprograme z metodo Java. Java metode uporabljajo drugačen nabor navodil. Navodila, kot so invokevirtual ali invokenonvirtual povzroči priklic metode Java in navodil, kot je vrnitev, areturn, ali ireturn povzroči vrnitev metode Java. The jsr navodilo ne povzroči priklica metode Java. Namesto tega povzroči preskok na drugo opcode znotraj iste metode. Prav tako ponovitev navodilo se ne vrne iz metode; namesto tega se vrne nazaj v kodo opcij po isti metodi, ki takoj sledi klicanju jsr navodila in njegovi operandi. Bytecode, ki izvajajo a končno
klavzulo imenujemo miniaturna podprogram, ker deluje kot majhen podprogram znotraj toka bajt kod ene same metode.
Morda mislite, da ponovitev navodilo naj vrne naslov vrnitve iz sklada, ker ga je tja potisnil jsr navodila. Ampak ne. Namesto tega se na začetku vsake podprograme povratni naslov izstreli z vrha sklada in shrani v lokalno spremenljivko - isto lokalno spremenljivko, iz katere je ponovitev navodila dobijo pozneje. Ta asimetričen način dela z naslovom za vrnitev je potreben, ker lahko končno klavzule (in zato miniaturne podprograme) povzročijo izjeme ali vključujejo vrnitev
, odmor
, ali nadaljujte
izjave. Zaradi te možnosti je dodatni povratni naslov, ki ga je na sklad potisnil jsr navodilo je treba takoj odstraniti iz sklada, zato ga še vedno ne bo, če končno
klavzula izstopa z odmor
, nadaljujte
, vrnitev
ali vržena izjema. Zato se povratni naslov shrani v lokalno spremenljivko na začetku katerega koli končno
miniaturna podprogram klavzule.
Za ponazoritev si oglejte naslednjo kodo, ki vključuje a končno
klavzula, ki izstopi s stavkom break. Rezultat te kode je, ne glede na parameter bVal, ki je bil poslan metodi surpriseTheProgrammer ()
, metoda vrne napačno
:
statično logično presenečenjeTheProgrammer (logični bVal) {while (bVal) {try {return true; } končno {odmor; }} vrne false; }
Zgornji primer prikazuje, zakaj je treba povratni naslov shraniti v lokalno spremenljivko na začetku datoteke končno
klavzulo. Zaradi končno
klavzula izstopi s prekinitvijo, nikoli ne izvrši ponovitev navodila. Kot rezultat, se JVM nikoli ne vrne, da bi dokončal "vrni se res
". Namesto tega gre samo naprej odmor
in se spusti mimo zaključne skodrane ograde medtem
izjavo. Naslednja izjava je "vrni false
, "kar natančno počne JVM.
Vedenje, ki ga kaže a končno
klavzula, ki izstopa z odmor
prikazuje tudi končno
klavzule, ki izstopajo z vrnitev
ali nadaljujte
ali z vrnitvijo izjeme. Če končno
klavzula izstopi iz katerega koli od teh razlogov, ponovitev navodilo na koncu končno
klavzula se nikoli ne izvrši. Zaradi ponovitev Navodilo ni zagotovljeno za izvedbo, nanj ni mogoče zanesti odstranitve povratnega naslova iz sklada. Zato je povratni naslov shranjen v lokalni spremenljivki na začetku datoteke končno
miniaturna podprogram klavzule.
Za popoln primer si oglejte naslednjo metodo, ki vsebuje poskusite
blok z dvema izstopnima točkama. V tem primeru sta obe izstopni točki vrnitev
izjave:
static int giveMeThatOldFashionedBoolean (logični bVal) {poskus {{if (bVal) {return 1; } vrni 0; } končno {System.out.println ("Staromodno."); }}
Zgornja metoda se prevede v naslednje bajtode:
// Zaporedje bajt kod za poskusni blok: 0 iload_0 // Potisni lokalno spremenljivko 0 (arg je poslan kot delitelj) 1 ifeq 11 // Potisni lokalno spremenljivko 1 (arg je poslan kot dividenda) 4 iconst_1 // Push int 1 5 istore_3 // Pop an int (the 1), shrani v lokalno spremenljivko 3 6 jsr 24 // Skoči na mini podprogram za zadnji člen 9 iload_3 // Potisni lokalno spremenljivko 3 (the 1) 10 ireturn // Vrni int na vrh stack (the 1) 11 iconst_0 // Push int 0 12 istore_3 // Pop an int (the 0), shrani v lokalno spremenljivko 3 13 jsr 24 // Skoči na mini podprogram za končno klavzulo 16 iload_3 // Push local spremenljivka 3 (0) 17 ireturn // Vrne int na vrh sklada (0) // Zaporedje bajt kod za ulovno klavzulo, ki ujame kakršno koli izjemo //, vrženo iz bloka try. 18 astore_1 // Postavi sklic na vrženo izjemo, shrani // v lokalno spremenljivko 1 19 jsr 24 // Skoči na mini podprogram za končni člen 22 aload_1 // Potisni sklic (na vrženo izjemo) iz // lokalna spremenljivka 1 23 athrow // Vrni isto izjemo // Miniaturna podprogram, ki izvaja zaključni blok. 24 astore_2 // Pop povratni naslov, shranite ga v lokalno spremenljivko 2 25 getstatic # 8 // Pridobite sklic na java.lang.System.out 28 ldc # 1 // Potisnite iz konstantnega področja 30 invokevirtual # 7 // Pokliči System.out.println () 33 ret 2 // Vrnitev na povratni naslov, shranjen v lokalni spremenljivki 2
Bytecode za poskusite
blok vključuje dva jsr navodila. Še eno jsr Navodila so v ulov
klavzulo. The ulov
klavzulo doda prevajalnik, ker če se med izvajanjem datoteke vrže izjema poskusite
blok, mora biti končni blok še vedno izveden. Zato je ulov
klavzula se samo sklicuje na miniaturno podprogram, ki predstavlja končno
, nato znova vrže isto izjemo. Tabela izjem za giveMeThatOldFashionedBoolean ()
Metoda, prikazana spodaj, kaže, da vsaka izjema, vržena med in vključno z naslovi 0 in 17 (vse bajtode, ki izvajajo poskusite
blok) obravnava ulov
klavzula, ki se začne na naslovu 18.
Tabela izjem: od do ciljne vrste 0 18 18 katera koli
Bytecode datoteke končno
klavzula se začne tako, da vrne naslov iz sklada in ga shrani v lokalno spremenljivko dva. Na koncu končno
klavzula, ponovitev navodilo vzame svoj povratni naslov s pravega mesta, lokalna spremenljivka dva.
HopAround: simulacija navideznega stroja Java
Spodnji programček prikazuje navidezni stroj Java, ki izvaja zaporedje bajt kod. Zaporedje bajt kod v simulaciji je ustvarilo javac
prevajalnik za hopAround ()
metoda spodaj prikazanega razreda:
razred Clown {static int hopAround () {int i = 0; while (true) {poskusite {poskusite {i = 1; } na koncu {// prva stavka na koncu i = 2; } i = 3; vrnitev i; // to se nikoli ne dokonča, ker je nadaljevanje} na koncu {// druga določba na koncu if (i == 3) {nadaljevanje; // to nadaljevanje preglasi povratni stavek}}}}}
Bytecode, ki jih generira javac
za hopAround ()
metode so prikazane spodaj: