Programování

Definované a předvedené klauzule Try-finally

Vítejte v další splátce Under The Hood. Tento sloupec poskytuje vývojářům v Javě pohled na záhadné mechanismy klikání a vrčení pod jejich spuštěnými programy Java. Článek z tohoto měsíce pokračuje v diskusi o instrukční sadě bajtových kódů virtuálního stroje Java (JVM). Zaměřuje se na způsob, jakým JVM zachází Konečně klauzule a bytové kódy, které jsou relevantní k těmto klauzulím.

Nakonec: Něco na rozveselení

Protože virtuální stroj Java provádí bajtové kódy, které představují program Java, může jedním z několika způsobů ukončit blok kódu - příkazy mezi dvěma shodnými složenými závorkami. Za prvé, JVM jednoduše mohl provést za závěrečnou složenou závorkou bloku kódu. Nebo se může setkat s příkazem break, continue nebo return, který způsobí, že vyskočí z bloku kódu odkudkoli uprostřed bloku. Nakonec může být vyvolána výjimka, která způsobí, že JVM buď skočí na odpovídající klauzuli catch, nebo, pokud neexistuje odpovídající klauzule catch, vlákno ukončit. S těmito potenciálními výstupními body existujícími v jednom bloku kódu je žádoucí mít snadný způsob, jak vyjádřit, že se něco stalo bez ohledu na to, jak je blok kódu opuštěn. V Javě je taková touha vyjádřena pomocí zkuste-konečně doložka.

Chcete-li použít a zkuste-konečně doložka:

  • uzavřít do a Snaž se - zablokovat kód, který má více výstupních bodů, a -

  • vložte do Konečně zablokovat kód, který se musí stát bez ohledu na to, jak Snaž se blok je ukončen.

Například:

zkuste {// Blok kódu s více výstupními body} konečně {// Blok kódu, který se vždy provede při ukončení bloku try, // bez ohledu na to, jak se ukončí blok try} 

Pokud nějaké máte chytit doložky spojené s Snaž se blok, musíte dát Konečně klauzule po všem chytit klauzule, jako v:

try {// Blok kódu s více výstupními body} catch (Cold e) {System.out.println ("Caught cold!"); } catch (APopFly e) {System.out.println ("Caught a pop fly!"); } catch (SomeonesEye e) {System.out.println ("Přitáhlo něčí oko!"); } nakonec {// Blok kódu, který se provede vždy, když je ukončen blok try, // bez ohledu na to, jak je blok try ukončen. System.out.println ("Je to něco na rozveselení?"); } 

Pokud během provádění kódu v rámci a Snaž se blok, je vyvolána výjimka, která je zpracována a chytit doložka spojená s Snaž se blok, Konečně klauzule bude provedena po chytit doložka. Například pokud a Studený výjimka je vyvolána během provádění příkazů (není zobrazeno) v Snaž se o blok výše by byl na standardní výstup zapsán následující text:

Nachlazení! Je to něco na rozveselení? 

Klauzule Try-finally v bajtových kódech

V bytecodes, Konečně klauzule fungují jako miniaturní podprogramy v rámci metody. V každém výstupním bodě uvnitř a Snaž se blok a související chytit klauzule, miniaturní podprogram, který odpovídá Konečně klauzule se nazývá. Po Konečně klauzule je dokončena - pokud je dokončena provedením za posledním příkazem v souboru Konečně klauzule, nikoli vyvoláním výjimky nebo provedením návratu, pokračování nebo přerušení - vrací se samotný miniaturní podprogram. Provádění pokračuje těsně za bodem, kde byl miniaturní podprogram volán na prvním místě, tedy Snaž se blok lze příslušným způsobem opustit.

Operační kód, který způsobí, že JVM přeskočí na miniaturní podprogram, je jsr návod. The jsr instrukce bere dvoubajtový operand, posun od umístění jsr instrukce, kde začíná miniaturní podprogram. Druhá varianta jsr instrukce je jsr_w, který plní stejnou funkci jako jsr ale trvá široký (čtyřbajtový) operand. Když JVM narazí na a jsr nebo jsr_w instrukce, vloží zpáteční adresu do zásobníku a poté pokračuje v provádění na začátku miniaturního podprogramu. Zpáteční adresa je posunutí bytového kódu bezprostředně za jsr nebo jsr_w instrukce a její operandy.

Po dokončení miniaturního podprogramu vyvolá ret instrukce, která se vrací z podprogramu. The ret instrukce bere jeden operand, index do lokálních proměnných, kde je uložena zpáteční adresa. Opcodes, které se zabývají Konečně klauzule jsou shrnuty v následující tabulce:

Konečně věty
Operační kódOperand (s)Popis
jsrbranchbyte1, branchbyte2posune zpáteční adresu, větve k odsazení
jsr_wbranchbyte1, branchbyte2, branchbyte3, branchbyte4posune zpáteční adresu, větve na široký offset
retindexvrací na adresu uloženou v indexu lokálních proměnných

Nezaměňujte miniaturní podprogram s metodou Java. Metody Java používají jinou sadu pokynů. Pokyny jako invokevirtual nebo invokenonvirtual způsobí vyvolání metody Java a pokynů, jako je vrátit se, návratnebo zpět způsobí návrat metody Java. The jsr instrukce nezpůsobí vyvolání metody Java. Místo toho způsobí skok na jiný operační kód v rámci stejné metody. Stejně tak ret instrukce se nevrátí z metody; spíše se vrátí zpět na operační kód ve stejné metodě, která bezprostředně následuje po volání jsr instrukce a její operandy. Bajtové kódy, které implementují a Konečně Klauzule se nazývají miniaturní podprogram, protože fungují jako malý podprogram v proudu bytecode jedné metody.

Možná si myslíte, že ret instrukce by měla vyskakovat zpáteční adresu ze zásobníku, protože tam ji tlačil jsr návod. Ale není. Místo toho se na začátku každého podprogramu zpáteční adresa vyskočí z horní části zásobníku a uloží se do místní proměnné - stejné místní proměnné, ze které ret instrukce později dostane. Tento asymetrický způsob práce se zpáteční adresou je nezbytný, protože konečně klauzule (a tedy miniaturní podprogramy) samy o sobě mohou vyvolat výjimky nebo zahrnout vrátit se, přestávkanebo pokračovat prohlášení. Z důvodu této možnosti byla extra zpáteční adresa, která byla do zásobníku vložena jsr instrukce musí být okamžitě odstraněna ze zásobníku, takže tam stále nebude, pokud Konečně klauzule končí s a přestávka, pokračovat, vrátit se, nebo vyvolána výjimka. Proto je zpáteční adresa uložena do místní proměnné na začátku kteréhokoli z nich Konečně doložka je miniaturní podprogram.

Pro ilustraci zvažte následující kód, který obsahuje a Konečně klauzule, která končí příkazem break. Výsledkem tohoto kódu je, že bez ohledu na parametr bVal předaný metodě překvapeníTheProgrammer (), metoda se vrací Nepravdivé:

 static boolean překvapeníTheProgrammer (boolean bVal) {while (bVal) {try {return true; } konečně {zlomit; }} vrátit false; } 

Výše uvedený příklad ukazuje, proč musí být zpáteční adresa uložena do místní proměnné na začátku Konečně doložka. Protože Konečně klauzule končí s přestávkou, nikdy neprovede ret návod. Ve výsledku se JVM nikdy nevrátí a nedokončí „návrat true"prohlášení. Místo toho jde jen o přestávka a padá dolů za závěrečnou složenou závorku zatímco prohlášení. Další prohlášení je „návrat false„, což je přesně to, co JVM dělá.

Chování zobrazené a Konečně klauzule, která končí s a přestávka je také zobrazeno Konečně klauzule, které opouštějí a vrátit se nebo pokračovatnebo vyvoláním výjimky. Pokud Konečně doložka končí z některého z těchto důvodů, ret instrukce na konci Konečně klauzule se nikdy neprovede. Protože ret není zaručeno provedení instrukce, nelze se na ni spoléhat při odstranění zpáteční adresy ze zásobníku. Zpáteční adresa je proto uložena do místní proměnné na začátku Konečně miniaturní podprogram doložky.

Úplný příklad zvažte následující metodu, která obsahuje a Snaž se blok se dvěma výstupními body. V tomto příkladu jsou oba výstupní body vrátit se prohlášení:

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

Výše uvedená metoda se kompiluje do následujících bajtových kódů:

// Sekvence bytecode pro blok try: 0 iload_0 // Push lokální proměnná 0 (arg předán jako dělitel) 1 ifeq 11 // Push lokální proměnná 1 (arg předán jako dividenda) 4 iconst_1 // Push int 1 5 istore_3 // Pop an int (the 1), store into local variable 3 6 jsr 24 // Skok na mini-podprogram pro definitivní klauzuli 9 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-subroutine for the finally clause 16 iload_3 // Push local proměnná 3 (0) 17 ireturn // Vrátit int v horní části zásobníku (0) // Sekvence bytecode pro klauzuli catch, která zachytí jakýkoli druh výjimky // vyvolána v rámci bloku try. 18 astore_1 // Popujte odkaz na vyvolanou výjimku, uložte // do lokální proměnné 1 19 jsr 24 // Přeskočte na mini-podprogram pro klauzuli nakonec 22 aload_1 // Posuňte odkaz (na vyvolanou výjimku) z // lokální proměnná 1 23 athrow // Obnoví stejnou výjimku // Miniaturní podprogram, který implementuje konečný blok. 24 astore_2 // Pop zpáteční adresu, uložte ji do místní proměnné 2 25 getstatic # 8 // Získejte odkaz na java.lang.System.out 28 ldc # 1 // Push z konstantního fondu 30 invokevirtual # 7 // Invoke System.out.println () 33 ret 2 // Návrat na zpáteční adresu uloženou v místní proměnné 2 

Bajtové kódy pro Snaž se blok obsahuje dva jsr instrukce. Další jsr instrukce je obsažena v chytit doložka. The chytit klauzule je přidána kompilátorem, protože pokud je vyvolána výjimka během provádění Snaž se blok, konečný blok musí být stále proveden. Proto chytit klauzule pouze vyvolá miniaturní podprogram, který představuje Konečně klauzule, pak vyvolá stejnou výjimku znovu. Tabulka výjimek pro giveMeThatOldFashionedBoolean () Metoda zobrazená níže označuje, že jakákoli výjimka vyvolaná mezi adresami 0 a 17 včetně (všechny bajtové kódy, které implementují Snaž se blok) zpracovává chytit klauzule, která začíná na adrese 18.

Tabulka výjimek: od do cílového typu 0 18 18 libovolná 

Bajtové kódy Konečně klauzule začíná vyskakováním zpáteční adresy ze zásobníku a jejím uložením do lokální proměnné dva. Na konci Konečně doložka, ret instrukce vezme zpáteční adresu ze správného místa, lokální proměnná dva.

HopAround: Simulace virtuálního stroje Java

Níže uvedený applet ukazuje virtuální stroj Java provádějící sekvenci bajtových kódů. Sekvence bytecode v simulaci byla vygenerována javac překladač pro hopAround () metoda třídy uvedené níže:

třída Klaun {static int hopAround () {int i = 0; while (true) {try {try {i = 1; } nakonec {// první klauzule konečně i = 2; } i = 3; návrat i; // toto se nikdy nedokončí kvůli pokračování} nakonec {// druhá klauzule konečně if (i == 3) {continue; // toto pokračování potlačí návratové prohlášení}}}}} 

Bajtové kódy generované javac pro hopAround () níže jsou uvedeny metody:

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