Programovanie

Definované a demonštrované klauzuly typu Try-konečne

Vitajte v ďalšej splátke produktu Pod kapotou. Tento stĺpec poskytuje vývojárom Java prehľad tajomných mechanizmov klikajúcich a vrčiacich pod ich spustenými programami Java. Tento mesiac článok pokračuje v diskusii o inštrukčnej sade bytových kódov virtuálneho stroja Java (JVM). Zameriava sa na spôsob, akým JVM narába konečne doložky a bajtkódy, ktoré sú relevantné k týmto doložkám.

Na záver: Niečo na povzbudenie

Keď virtuálny stroj Java vykonáva bajtové kódy, ktoré predstavujú program Java, môže jedným z niekoľkých spôsobov opustiť blok kódu - príkazy medzi dvoma zodpovedajúcimi zloženými zátvorkami. Za prvé, JVM jednoducho mohol vykonať za zatváracou zloženou zátvorkou bloku kódu. Alebo by sa mohol stretnúť s príkazom prerušenia, pokračovania alebo vrátenia, ktorý spôsobí, že vyskočí z bloku kódu odkiaľkoľvek uprostred bloku. Nakoniec by mohla byť vyvolaná výnimka, ktorá spôsobí, že JVM buď skočí na zodpovedajúcu klauzulu catch, alebo, ak neexistuje zodpovedajúca klauzula catch, vlákno ukončí. Pretože tieto potenciálne výstupné body existujú v jednom bloku kódu, je žiaduce mať jednoduchý spôsob, ako vyjadriť, že sa niečo stalo bez ohľadu na to, ako je blok kódu opustený. V Jave je takáto túžba vyjadrená a skús-konečne doložka.

Ak chcete použiť a skús-konečne doložka:

  • uzavrieť do a skús - zablokovať kód s viacerými výstupnými bodmi a -

  • dať do a konečne Blokovať kód, ktorý sa musí stať bez ohľadu na to, ako skús blok je opustený.

Napríklad:

skúste {// Blok kódu s viacerými výstupnými bodmi} konečne {// Blok kódu, ktorý sa vykoná vždy, keď je blok try ukončený, // bez ohľadu na to, ako je blok try ukončený} 

Ak nejaké máte chytiť doložky spojené s skús blok, musíte dať konečne doložka po všetkých chytiť doložky, ako v:

try {// Blok kódu s viacerými výstupnými bodmi} catch (Cold e) {System.out.println ("Caught cold!"); } chytit (APopFly e) {System.out.println ("Caught a pop fly!"); } catch (SomeonesEye e) {System.out.println ("Prichytil niekoho oko!"); } konečne {// Blok kódu, ktorý sa vykoná vždy pri ukončení bloku try, // bez ohľadu na to, ako je blok try ukončený. System.out.println ("Je to niečo, na čo by ste mali fandiť?"); } 

Ak počas vykonávania kódu v rámci a skús blok, vyvolá sa výnimka, ktorú spracuje a chytiť doložka spojená s skús blok, konečne doložka sa vykoná po chytiť doložka. Napríklad ak a Chladný výnimka je vyvolaná počas vykonávania príkazov (nezobrazené) v skús blok vyššie, na štandardný výstup by sa zapísal nasledujúci text:

Prechladnutý! Je to niečo na povzbudenie? 

Klauzuly try-konečne v bytových kódoch

V bytecodes, konečne vety pôsobia ako miniatúrne podprogramy v rámci metódy. Na každom výstupnom bode vo vnútri a skús blok a s ním spojené chytiť klauzuly, miniatúrny podprogram, ktorý zodpovedá konečne doložka sa volá. Po konečne klauzula sa dokončuje - pokiaľ sa dokončuje vykonaním za posledným príkazom v kóde konečne klauzula, nie zrušením výnimky alebo vykonaním návratu, pokračovať alebo zlomiť - vráti sa samotný miniatúrny podprogram. Poprava pokračuje tesne za bodom, kde bol na prvom mieste zavolaný miniatúrny podprogram, teda skús blok je možné vhodným spôsobom opustiť.

Operačný kód, ktorý spôsobí, že JVM preskočí na miniatúrny podprogram, je spol poučenie. The spol inštrukcia berie dvojbajtový operand, odsadenie od polohy spol inštrukcia, kde sa miniatúrny podprogram začína. Druhý variant spol inštrukcia je jsr_w, ktorá vykonáva rovnakú funkciu ako spol ale berie široký (štvorbajtový) operand. Keď JVM narazí na a spol alebo jsr_w inštrukcia, natlačí spiatočnú adresu do zásobníka, potom pokračuje v vykonávaní na začiatku miniatúrneho podprogramu. Spiatočná adresa je posunutím bajtkódu bezprostredne za spol alebo jsr_w inštrukcia a jej operandy.

Po dokončení miniatúrneho podprogramu vyvolá ret inštrukcia, ktorá sa vráti z podprogramu. The ret inštrukcia vezme jeden operand, index do lokálnych premenných, kde je uložená spiatočná adresa. Protokoly, ktoré sa zaoberajú konečne doložky sú zhrnuté v nasledujúcej tabuľke:

Nakoniec klauzuly
Operačný kódOperand (y)Popis
spolbranchbyte1, branchbyte2posunie spiatočnú adresu, vetvy na odsadenie
jsr_wbranchbyte1, branchbyte2, branchbyte3, branchbyte4posunie spiatočnú adresu, vetvy na široký offset
retindexvráti na adresu uloženú v indexe lokálnych premenných

Nezamieňajte miniatúrny podprogram s metódou Java. Metódy Java používajú inú sadu pokynov. Pokyny ako napr invokevirtual alebo invokenonvirtual spôsobí vyvolanie metódy Java a inštrukcie ako napr návrat, návratalebo naspäť spôsobiť návrat metódy Java. The spol inštrukcia nespôsobuje vyvolanie metódy Java. Namiesto toho spôsobí v rámci rovnakej metódy skok na iný operačný kód. Rovnako tak ret inštrukcia sa nevráti z metódy; skôr sa vráti späť k operačnému kódu rovnakou metódou, ktorá bezprostredne nasleduje po volaní spol inštrukcia a jej operandy. Bajtové kódy, ktoré implementujú a konečne Klauzula sa nazýva miniatúrny podprogram, pretože pôsobia ako malý podprogram v prúde bytecode jednej metódy.

Možno si myslíte, že ret inštrukcia by mala vyskladniť spiatočnú adresu zo zásobníka, pretože práve tam ju vytlačil spol poučenie. Ale nie je. Namiesto toho sa na začiatku každého podprogramu návratová adresa vysunie z hornej časti zásobníka a uloží sa do miestnej premennej - tej istej miestnej premennej, z ktorej ret inštrukcia to dostane neskôr. Tento asymetrický spôsob práce so spiatočnou adresou je nevyhnutný, pretože nakoniec samotné klauzuly (a teda miniatúrne podprogramy) môžu spôsobiť výnimky alebo zahrnúť návrat, prestávkaalebo ďalej Vyhlásenia. Z dôvodu tejto možnosti bola dodatočná návratová adresa, ktorá bola do zásobníka vložená serverom spol inštrukcia musí byť ihneď odstránená zo stohu, takže tam stále nebude, ak konečne doložka končí s a prestávka, ďalej, návrat, alebo hodená výnimka. Preto je spiatočná adresa uložená do lokálnej premennej na začiatku ktorejkoľvek z nich konečne miniatúrny podprogram doložky.

Pre ilustráciu zvážte nasledujúci kód, ktorý obsahuje a konečne klauzula, ktorá končí príkazom break. Výsledkom tohto kódu je, že bez ohľadu na parameter bVal odovzdaný metóde prekvapenieTheProgrammer (), metóda sa vráti nepravdivé:

 static boolean prekvapenieTheProgrammer (boolean bVal) {while (bVal) {try {return true; } konečne {prestávka; }} return false; } 

Vyššie uvedený príklad ukazuje, prečo musí byť spiatočná adresa uložená do lokálnej premennej na začiatku konečne doložka. Pretože konečne klauzula končí s prerušením, nikdy sa nevykoná ret poučenie. Výsledkom je, že JVM sa nikdy nevráti a nedokončí „návrat pravdivý"vyhlásenie. Namiesto toho ide ďalej o." prestávka a padá dole za zatváraciu kučeravú zátvorku zatiaľ čo vyhlásenie. Nasledujúce vyhlásenie je „návrat nepravdivý„, čo je presne to, čo JVM robí.

Správanie zobrazené a konečne klauzula, ktorá končí s a prestávka sa tiež zobrazuje ako konečne doložky, ktoré vystupujú s a návrat alebo ďalej, alebo zrušením výnimky. Ak konečne doložka končí z niektorého z týchto dôvodov, ret inštrukcia na konci konečne doložka sa nikdy nevykoná. Pretože ret inštrukcia nie je zaručene vykonaná, nemožno sa na ňu spoľahnúť pri odstránení spiatočnej adresy zo zásobníka. Preto je spiatočná adresa uložená do lokálnej premennej na začiatku konečne miniatúrny podprogram doložky.

Pre úplný príklad zvážte nasledujúcu metódu, ktorá obsahuje a skús blok s dvoma výstupnými bodmi. V tomto príklade sú oba výstupné body návrat Vyhlásenia:

 static int giveMeThatOldFashionedBoolean (boolean bVal) {try {if (bVal) {return 1; } návrat 0; } konečne {System.out.println ("Staromódne."); }} 

Vyššie uvedená metóda sa kompiluje do nasledujúcich bajtových kódov:

// Sekvencia bytecode pre blok try: 0 iload_0 // Zatlač lokálnu premennú 0 (arg odovzdaný ako deliteľ) 1 ifeq 11 // Stlač lokálnu premennú 1 (arg odovzdaný ako dividenda) 4 iconst_1 // Zatlač int 1 5 istore_3 // Vyklopte int (1), uložte do lokálnej premennej 3 6 jsr 24 // Preskočte na mini-podprogram pre klauzulu finally iload_3 // Push local variable 3 (the 1) 10 ireturn // Return int on the top stack (the 1) 11 iconst_0 // Push int 0 12 istore_3 // Pop an int (the 0), store into local variable 3 13 jsr 24 // Jump to the mini-subrutine for the finally clause 16 iload_3 // Push local premenná 3 (0) 17 ireturn // Return int na vrchu zásobníka (0) // Sekvencia bytecode pre klauzulu catch, ktorá zachytáva akýkoľvek druh výnimky // vyvolaná zvnútra bloku try. 18 astore_1 // Vyklopte odkaz na vyvolanú výnimku, uložte // do lokálnej premennej 1 19 jsr 24 // Prejdite na mini-podprogram pre klauzulu finally 22 aload_1 // Posuňte odkaz (na vyvolanú výnimku) z // lokálna premenná 1 23 athrow // Obnoví rovnakú výnimku // Miniatúrny podprogram, ktorý implementuje blok konečne. 24 astore_2 // Vyplňte spiatočnú adresu, uložte ju do lokálnej premennej 2 25 getstatic # 8 // Získajte odkaz na java.lang.System.out 28 ldc # 1 // Zatlačte z konštantnej oblasti 30 invokevirtual # 7 // Vyvolať System.out.println () 33 ret 2 // Návrat na návratovú adresu uloženú v lokálnej premennej 2 

Bajtkódy pre skús blok zahŕňajú dva spol inštrukcie. Ďalší spol inštrukcia je obsiahnutá v chytiť doložka. The chytiť klauzula je pridaná kompilátorom, pretože ak dôjde k výnimke počas vykonávania skús blok, blok konečne musí byť stále vykonaný. Preto chytiť doložka iba vyvoláva miniatúrny podprogram, ktorý predstavuje konečne klauzula, potom sa hodí rovnaká výnimka znova. Tabuľka výnimiek pre giveMeThatOldFashionedBoolean () Metóda uvedená nižšie naznačuje, že akákoľvek výnimka vyvolaná medzi adresami 0 a 17 vrátane (všetky bytecodes, ktoré implementujú skús blok) vybavuje chytiť doložka, ktorá sa začína na adrese 18.

Tabuľka výnimiek: od do cieľového typu 0 18 18 ľubovoľná 

Bajtkódy kódu konečne doložka začína vyklopením spiatočnej adresy zo zásobníka a jej uložením do lokálnej premennej dva. Na konci konečne doložka, ret inštrukcia vezme svoju spiatočnú adresu zo správneho miesta, lokálna premenná dva.

HopAround: Simulácia virtuálneho stroja Java

Nižšie uvedený applet demonštruje virtuálny stroj Java vykonávajúci sekvenciu bajtových kódov. Sekvenciu bajtových kódov v simulácii vygeneroval javac kompilátor pre hopAround () metóda triedy uvedená nižšie:

trieda Klaun {static int hopAround () {int i = 0; while (true) {try {try {i = 1; } konečne {// prvá klauzula konečne i = 2; } i = 3; návrat i; // toto sa nikdy nedokončí z dôvodu continue} konečne {// druhá klauzula konečne if (i == 3) {continue; // toto pokračovanie potlačí návratové vyhlásenie}}}}} 

Bajtové kódy vygenerované javac pre hopAround () nižšie sú uvedené:

$config[zx-auto] not found$config[zx-overlay] not found