Programmering

Prøv endelig klausuler defineret og demonstreret

Velkommen til en anden rate af Under kølerhjelmen. Denne kolonne giver Java-udviklere et glimt af de mystiske mekanismer, der klikker og hvirvler under deres kørende Java-programmer. Denne måneds artikel fortsætter diskussionen af ​​bytecode-instruktionssættet på den virtuelle Java-maskine (JVM). Dens fokus er den måde, hvorpå JVM håndterer langt om længe klausuler og de bytekoder, der er relevante for disse klausuler.

Endelig: Noget at heje på

Da den virtuelle Java-maskine udfører bytekoder, der repræsenterer et Java-program, kan den muligvis afslutte en kodeblok - udsagnene mellem to matchende krøllede seler - på en af ​​flere måder. For det første kunne JVM simpelthen udføre forbi den lukkede krøllede afstivning af kodeblokken. Eller det kan støde på en pause, fortsætte eller returnere erklæring, der får det til at springe ud af kodeblokken et eller andet sted midt i blokken. Endelig kan en undtagelse kastes, der får JVM til enten at hoppe til en matchende fangstklausul eller, hvis der ikke er en matchende fangstklausul, at afslutte tråden. Da disse potentielle udgangspunkter findes inden for en enkelt kodeblok, er det ønskeligt at have en nem måde at udtrykke, at der skete noget, uanset hvordan en blok med kode afsluttes. I Java udtrykkes et sådant ønske med en prøv endelig klausul.

At bruge en prøv endelig klausul:

  • vedlægge en prøve blokere koden, der har flere udgangspunkter, og

  • læg i en langt om længe blokere den kode, der skal ske, uanset hvordan prøve blok er afsluttet.

For eksempel:

prøv {// Kodeblok med flere udgangspunkter} endelig {// Kodeblok, der altid udføres, når prøveblokken afsluttes, // uanset hvordan prøveblokken afsluttes} 

Hvis du har nogen fangst klausuler forbundet med prøve blok, skal du sætte langt om længe klausul efter alle fangst klausuler som i:

prøv {// Kodeblok med flere udgangspunkter} fangst (kold e) {System.out.println ("Fanget koldt!"); } fange (APopFly e) {System.out.println ("Fanget en popflyvning!"); } fange (SomeonesEye e) {System.out.println ("Fanget nogens øje!"); } endelig {// Kodeblok, der altid udføres, når prøveblokken afsluttes, // uanset hvordan prøveblokken afsluttes. System.out.println ("Er det noget at juble for?"); } 

Hvis der under udførelse af koden inden for en prøve blok, kastes en undtagelse, der håndteres af en fangst klausul forbundet med prøve blokere, den langt om længe klausul udføres efter fangst klausul. For eksempel, hvis en Kold undtagelse kastes under udførelse af udsagnene (ikke vist) i prøve ovenfor, ville følgende tekst blive skrevet til standardoutputtet:

Fanget koldt! Er det noget at heje på? 

Prøv endelig klausuler i bytekoder

I bytekoder, langt om længe klausuler fungerer som miniaturesubrutiner inden for en metode. Ved hvert udgangssted inde i a prøve blok og dens tilknyttede fangst klausuler, den miniature subrutine, der svarer til langt om længe klausul kaldes. Efter langt om længe klausul afsluttes - så længe den fuldføres ved at udføre forbi den sidste sætning i langt om længe klausul, ikke ved at kaste en undtagelse eller udføre en retur, fortsætte eller bryde - selve miniaturens underrutine vender tilbage. Udførelsen fortsætter lige forbi det punkt, hvor miniaturesubrutinen blev kaldt i første omgang, så prøve blok kan afsluttes på passende måde.

Opkoden, der får JVM til at hoppe til en mini-subrutine, er jsr instruktion. Det jsr instruktion tager en to-byte operand, forskydningen fra placeringen af jsr instruktion, hvor miniaturesubrutinen begynder. En anden variant af jsr instruktion er jsr_w, som udfører den samme funktion som jsr men tager en bred (fire-byte) operand. Når JVM møder en jsr eller jsr_w instruktion, det skubber en returadresse på stakken og fortsætter derefter udførelsen i starten af ​​miniaturens underrutine. Returadressen er forskydningen af ​​bytekoden umiddelbart efter jsr eller jsr_w instruktion og dens operander.

Når en miniaturesubrutine er afsluttet, påberåber den sig ret instruktion, som vender tilbage fra subrutinen. Det ret instruktion tager en operand, et indeks i de lokale variabler, hvor returadressen er gemt. Opkoderne, der beskæftiger sig med langt om længe klausuler er opsummeret i følgende tabel:

Endelig klausuler
OpkodeOperand (er)Beskrivelse
jsrbranchbyte1, branchbyte2skubber returadressen, forgrener sig til offset
jsr_wbranchbyte1, branchbyte2, branchbyte3, branchbyte4skubber returadressen, forgrener sig til bred offset
retindeksvender tilbage til den adresse, der er gemt i det lokale variabelindeks

Forveks ikke en mini-subrutine med en Java-metode. Java-metoder bruger et andet sæt instruktioner. Instruktioner som f.eks invokevirtual eller invokenonvirtual forårsage, at en Java-metode påberåbes, og instruktioner som f.eks Vend tilbage, areturn, eller ireturn få en Java-metode til at vende tilbage. Det jsr instruktion medfører ikke, at en Java-metode påberåbes. I stedet forårsager det et spring til en anden opcode inden for samme metode. Ligeledes ret instruktion vender ikke tilbage fra en metode; snarere vender det tilbage til opkoden i den samme metode, der straks følger opkaldet jsr instruktion og dens operander. Bykoderne, der implementerer en langt om længe klausul kaldes en miniature subrutine, fordi de fungerer som en lille subrutine inden for bytecode-strømmen af ​​en enkelt metode.

Du tror måske, at ret instruktion skal pope returadressen fra stakken, for det er her, den blev skubbet af jsr instruktion. Men det gør det ikke. I stedet for, ved starten af ​​hver underrutine, returneres returadressen fra toppen af ​​stakken og lagres i en lokal variabel - den samme lokale variabel, hvorfra ret instruktion senere får det. Denne asymmetriske måde at arbejde med returadressen på er nødvendig, fordi endelig klausuler (og derfor miniature subrutiner) selv kan kaste undtagelser eller inkludere Vend tilbage, pause, eller Blive ved udsagn. På grund af denne mulighed blev den ekstra returadresse, der blev skubbet på stakken af jsr instruktion skal fjernes fra stakken med det samme, så den vil ikke stadig være der, hvis langt om længe klausul udgår med en pause, Blive ved, Vend tilbageeller kastet undtagelse. Derfor lagres returadressen i en lokal variabel i starten af ​​en hvilken som helst langt om længe klausulens miniature subrutine.

Som en illustration skal du overveje følgende kode, som inkluderer en langt om længe klausul, der afsluttes med en pauseopgørelse. Resultatet af denne kode er, at uanset parameteren bVal videregivet til metoden surpriseTheProgrammer (), metoden vender tilbage falsk:

 statisk boolsk overraskelseTheProgrammer (boolsk bVal) {mens (bVal) {prøv {returner sand; } endelig {pause; }} returner falsk; } 

Eksemplet ovenfor viser, hvorfor returadressen skal gemmes i en lokal variabel i begyndelsen af langt om længe klausul. Fordi langt om længe klausul udgår med en pause, den udfører aldrig ret instruktion. Som et resultat går JVM aldrig tilbage for at afslutte "returner sandt"erklæring. I stedet fortsætter det bare med pause og falder ned forbi den lukkende krøllede bøjle af mens udmelding. Den næste erklæring er "returner falsk, "hvilket er netop hvad JVM gør.

Den adfærd, som a langt om længe klausul, der udløber med en pause er også vist af langt om længe klausuler, der går ud med en Vend tilbage eller Blive vedeller ved at kaste en undtagelse. Hvis en langt om længe klausul udløber af en af ​​disse grunde, ret instruktion i slutningen af langt om længe klausul udføres aldrig. Fordi ret instruktioner garanteres ikke at blive udført, det kan ikke stole på at fjerne returadressen fra stakken. Derfor lagres returadressen i en lokal variabel i begyndelsen af langt om længe klausulens miniature subrutine.

For et komplet eksempel, overvej følgende metode, der indeholder en prøve blok med to udgangspunkter. I dette eksempel er begge udgangspunkter Vend tilbage udsagn:

 statisk int giveMeThatOldFashionedBoolean (boolsk bVal) {prøv {hvis (bVal) {retur 1; } returner 0; } endelig {System.out.println ("Fik gammeldags."); }} 

Ovenstående metode kompileres med følgende bytekoder:

// Bytecode-sekvensen for prøveblokken: 0 iload_0 // Push lokal variabel 0 (arg bestået som divisor) 1 ifeq 11 // Push lokal variabel 1 (arg bestået som udbytte) 4 iconst_1 // Push int 1 5 istore_3 // Pop en int (1), gem i lokal variabel 3 6 jsr 24 // Spring til mini-subrutinen for den sidste paragraf 9 iload_3 // Skub lokal variabel 3 (1) 10 ireturn // Returner int oven på stak (1) 11 ikonst_0 // Push int 0 12 istore_3 // Pop et int (0), gem i lokal variabel 3 13 jsr 24 // Spring til mini-subrutinen for den sidste paragraf 16 iload_3 // Push local variabel 3 (0) 17 ireturn // Returner int på toppen af ​​stakken (0) // Bytecode-sekvensen for en fangstklausul, der fanger enhver form for undtagelse // kastet fra prøveblokken. 18 astore_1 // Pop henvisningen til den kastede undtagelse, gem // i lokal variabel 1 19 jsr 24 // Spring til mini-subrutinen for den endelige paragraf 22 aload_1 // Skub henvisningen (til den kastede undtagelse) fra // lokal variabel 1 23 athrow // Gentag den samme undtagelse // Miniaturens underrutine, der implementerer den endelige blok. 24 astore_2 // Pop returadressen, gem den i lokal variabel 2 25 getstatic # 8 // Få en reference til java.lang.System.out 28 ldc # 1 // Skub fra den konstante pool 30 invokevirtual # 7 // Invoke System.out.println () 33 ret 2 // Vend tilbage til returadresse gemt i lokal variabel 2 

Bykoderne til prøve blok inkluderer to jsr instruktioner. En anden jsr instruktion er indeholdt i fangst klausul. Det fangst klausul tilføjes af kompilatoren, fordi hvis en undtagelse kastes under udførelsen af prøve blok, den sidste blok skal stadig udføres. Derfor er den fangst klausul påberåber sig blot den miniaturesubrutine, der repræsenterer langt om længe klausul, kaster derefter den samme undtagelse igen. Undtagelsestabellen for giveMeThatOldFashionedBoolean () Metoden, vist nedenfor, angiver, at enhver undtagelse, der smides mellem og inkluderer adresserne 0 og 17 (alle bytekoder, der implementerer prøve blok) håndteres af fangst klausul, der starter på adresse 18.

Undtagelsestabel: fra til måltype 0 18 18 enhver 

Bytekoderne til langt om længe klausul begynder med at poppe returadressen fra stakken og gemme den i lokal variabel to. I slutningen af langt om længe klausul, den ret instruktion tager sin returadresse fra det rette sted, lokal variabel to.

HopAround: En Java-virtuel maskinsimulering

Appletten nedenfor demonstrerer en Java-virtuel maskine, der udfører en sekvens af bytekoder. Bytecode-sekvensen i simuleringen blev genereret af javac kompilator til hopAround () metode i klassen vist nedenfor:

klasse Clown {statisk int hopAround () {int i = 0; mens (sand) {prøv {prøv {i = 1; } endelig {// den første endelig klausul i = 2; } i = 3; returnere i; // dette fuldender aldrig, på grund af fortsættelsen} endelig {// den anden endelig klausul, hvis (i == 3) {fortsætter; // dette fortsætter tilsidesætter returerklæringen}}}}} 

Bykoderne genereret af javac til hopAround () metode er vist nedenfor: