Programmering

Prøv endelig klausuler definert og demonstrert

Velkommen til en annen del av Under panseret. Denne kolonnen gir Java-utviklere et glimt av de mystiske mekanismene som klikker og snurrer under deres kjørende Java-programmer. Denne månedens artikkel fortsetter diskusjonen av bytecode instruksjonssettet til Java virtual machine (JVM). Fokuset er måten JVM håndterer endelig klausuler og bytekoder som er relevante for disse klausulene.

Til slutt: Noe å heie på

Når den virtuelle Java-maskinen utfører bytekodene som representerer et Java-program, kan den avslutte en kodeblokk - utsagnene mellom to matchende krøllete bukseseler - på en av flere måter. For det første kunne JVM bare utføre forbi den lukkende krøllbøylen til kodeblokken. Eller det kan møte en pause, fortsette eller returnere uttalelse som får den til å hoppe ut av kodeblokken fra et sted midt i blokken. Til slutt kan et unntak kastes som får JVM til å hoppe til en matchende fangstklausul, eller, hvis det ikke er en matchende fangstklausul, å avslutte tråden. Med disse potensielle utgangspunktene som finnes i en enkelt kodeblokk, er det ønskelig å ha en enkel måte å uttrykke at noe skjedde uansett hvordan en blokk med kode avsluttes. I Java uttrykkes et slikt ønske med a prøv-endelig klausul.

Å bruke en prøv-endelig klausul:

  • legg inn i en prøve blokker koden som har flere utgangspunkter, og

  • sette i en endelig blokker koden som må skje uansett hvordan prøve blokk er avsluttet.

For eksempel:

prøv {// Kodeblokk med flere utgangspunkter} til slutt {// Kodeblokk som alltid utføres når prøveblokken avsluttes, // uansett hvordan prøveblokken avsluttes} 

Hvis du har noen å fange klausuler knyttet til prøve blokker, må du sette endelig klausul etter alle å fange klausuler, som i:

prøv {// Kodeblokk med flere utgangspunkter} catch (Cold e) {System.out.println ("Fanget kaldt!"); } catch (APopFly e) {System.out.println ("Fanget en popfly!"); } catch (SomeonesEye e) {System.out.println ("Fanget noens øye!"); } til slutt {// Kodeblokk som alltid utføres når prøveblokken avsluttes, // uansett hvordan prøveblokken avsluttes. System.out.println ("Er det noe å heie på?"); } 

Hvis under utførelse av koden i en prøve blokk, kastes et unntak som håndteres av en å fange klausul knyttet til prøve blokk, den endelig klausul vil bli utført etter å fange klausul. For eksempel hvis en Kald unntak kastes under gjennomføring av uttalelsene (ikke vist) i prøve blokken ovenfor, vil følgende tekst bli skrevet til standardutgangen:

Fikk kaldt! Er det noe å heie på? 

Prøv endelig klausuler i bytekoder

I bytekoder, endelig klausuler fungerer som miniatyrunderrutiner innenfor en metode. Ved hvert utgangssted inne i prøve blokkering og tilhørende å fange klausuler, miniatyrundervisningen som tilsvarer endelig klausul kalles. Etter endelig klausul fullfører - så lenge den fullføres ved å kjøre forbi den siste setningen i endelig klausul, ikke ved å kaste et unntak eller utføre en retur, fortsette eller bryte - selve miniatyrundervisningen kommer tilbake. Utførelsen fortsetter like forbi punktet der miniatyrundervisningen ble kalt i utgangspunktet, så prøve blokkering kan avsluttes på riktig måte.

Opkoden som får JVM til å hoppe til en miniatyrundervisning er jsr instruksjon. De jsr instruksjon tar en to-byte operand, forskyvet fra plasseringen av jsr instruksjon der miniatyrsubrutinen begynner. En annen variant av jsr instruksjon er jsr_w, som utfører samme funksjon som jsr men tar en bred (fire-byte) operand. Når JVM møter en jsr eller jsr_w instruksjon, den skyver en returadresse på bunken, og fortsetter deretter utførelsen ved starten av miniatyrundrutinen. Returadressen er forskyvning av bytekoden umiddelbart etter jsr eller jsr_w instruksjon og dens operander.

Etter at en miniatyrundervisning er fullført, påkaller den ret instruksjon, som kommer tilbake fra underrutinen. De ret instruksjon tar en operand, en indeks i de lokale variablene der returadressen er lagret. Opkodene som håndterer endelig klausuler er oppsummert i følgende tabell:

Endelig klausuler
OpcodeOperand (er)Beskrivelse
jsrbranchbyte1, branchbyte2skyver returadressen, forgrener seg for å kompensere
jsr_wbranchbyte1, branchbyte2, branchbyte3, branchbyte4skyver returadressen, forgrener seg til bred forskyvning
retindeksgår tilbake til adressen som er lagret i lokal variabelindeks

Ikke forveksle en miniatyrundervisning med en Java-metode. Java-metoder bruker et annet sett med instruksjoner. Instruksjoner som invokevirtual eller invokenonvirtual føre til at en Java-metode påberopes, og instruksjoner som komme tilbake, en retur, eller jeg kommer tilbake føre til at en Java-metode kommer tilbake. De jsr instruksjon fører ikke til at en Java-metode blir påkalt. I stedet forårsaker det et hopp til en annen opcode innen samme metode. Likeledes, den ret instruksjon kommer ikke tilbake fra en metode; snarere går den tilbake til opkoden på samme måte som umiddelbart følger anropet jsr instruksjon og dens operander. Bytekodene som implementerer en endelig klausul kalles en miniatyrsubrutine fordi de fungerer som en liten subrutine i bytekodestrømmen til en enkelt metode.

Du tror kanskje at ret instruksjonen skal skyve returadressen fra bunken, fordi det er der den ble presset av jsr instruksjon. Men det gjør det ikke. I stedet, ved starten av hver underrutine, blir returadressen poppet av toppen av stabelen og lagret i en lokal variabel - den samme lokale variabelen som ret instruksjon får det senere. Denne asymmetriske måten å jobbe med returadressen på er nødvendig fordi endelig klausuler (og derfor miniatyrunderviser) selv kan kaste unntak eller inkludere komme tilbake, gå i stykker, eller Fortsette uttalelser. På grunn av denne muligheten ble den ekstra returadressen som ble skjøvet på bunken av jsr instruksjonen må fjernes fra bunken med en gang, så den vil fortsatt ikke være der hvis endelig klausul går ut med en gå i stykker, Fortsette, komme tilbake, eller kastet unntak. Derfor lagres returadressen i en lokal variabel ved starten av hvilken som helst endelig klausulens miniatyrundervisning.

Ta en titt på følgende kode, som inkluderer en endelig klausul som går ut med et brudduttalelse. Resultatet av denne koden er at, uavhengig av parameteren bVal overført til metoden surpriseTheProgrammer (), returnerer metoden falsk:

 statisk boolsk overraskelseTheProgrammer (boolsk bVal) {mens (bVal) {prøv {return true; } til slutt {pause; }} returner falsk; } 

Eksemplet ovenfor viser hvorfor returadressen må lagres i en lokal variabel i begynnelsen av endelig klausul. Fordi det endelig klausul går ut med en pause, utfører den aldri ret instruksjon. Som et resultat går JVM aldri tilbake for å fullføre "returner sant"uttalelse. I stedet fortsetter det bare med gå i stykker og faller ned forbi den lukkende krøllbøylen til samtidig som uttalelse. Neste uttalelse er "returner falsk, "som er nettopp det JVM gjør.

Oppførselen vist av a endelig klausul som går ut med en gå i stykker er også vist av endelig klausuler som går ut med en komme tilbake eller Fortsette, eller ved å kaste et unntak. Hvis en endelig klausul utgår av noen av disse grunnene, ret instruksjon på slutten av endelig klausul blir aldri utført. Fordi det ret instruksjon er ikke garantert å bli utført, det kan ikke stole på å fjerne returadressen fra bunken. Derfor lagres returadressen i en lokal variabel i begynnelsen av endelig klausulens miniatyrundervisning.

For et komplett eksempel, vurder følgende metode, som inneholder en prøve blokk med to utgangspunkter. I dette eksemplet er begge utgangspunkter komme tilbake uttalelser:

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

Ovennevnte metode samler seg til følgende bytekoder:

// Bytekodesekvensen for prøveblokken: 0 iload_0 // Push local variable 0 (arg passed as divisor) 1 ifeq 11 // Push local variable 1 (arg passed as dividend) 4 iconst_1 // Push int 1 5 istore_3 // Popp en int (1), lagre i lokal variabel 3 6 jsr 24 // Hopp til mini-subrutinen for den endelige paragrafen 9 iload_3 // Trykk lokal variabel 3 (1) 10 ireturn // Return int på toppen av stack (the) 11 iconst_0 // Push int 0 12 istore_3 // Pop an int (the 0), lagre i lokal variabel 3 13 jsr 24 // Jump to the mini-subrutine for the final paragraf 16 iload_3 // Push local variabel 3 (0) 17 ireturn // Returner int på toppen av stakken (0) // Bytkodesekvensen for en fangstklausul som fanger ethvert unntak // kastet fra prøveblokken. 18 astore_1 // Pop henvisningen til kastet unntak, lagre // i lokal variabel 1 19 jsr 24 // Hopp til mini-underrutinen for den endelige paragrafen 22 aload_1 // Skyv referansen (til kastet unntak) fra // lokal variabel 1 23 athrow // Rethrow the same exception // Miniatyrundrutinen som implementerer den endelige blokken. 24 astore_2 // Pop returadressen, lagre den i lokal variabel 2 25 getstatic # 8 // Få en referanse til java.lang.System.out 28 ldc # 1 // Push from the constant pool 30 invokevirtual # 7 // Invoke System.out.println () 33 ret 2 // Gå tilbake til returadressen som er lagret i lokal variabel 2 

Bytekodene for prøve blokker inkluderer to jsr bruksanvisning. En annen jsr instruksjonene er inneholdt i å fange klausul. De å fange klausul er lagt til av kompilatoren fordi hvis et unntak blir kastet under utførelsen av prøve blokk, den endelige blokken må fortsatt utføres. derfor å fange klausul påberoper seg bare miniatyrundervisningen som representerer endelig klausul, kaster deretter det samme unntaket igjen. Unntakstabellen for giveMeThatOldFashionedBoolean () Metoden, vist nedenfor, indikerer at unntak som kastes mellom og inkluderer adressene 0 og 17 (alle bytekodene som implementerer prøve blokk) håndteres av å fange klausul som starter på adresse 18.

Unntakstabell: fra til måltype 0 18 18 any 

Bytekodene til endelig klausul begynner med å poppe returadressen fra bunken og lagre den i lokal variabel to. På slutten av endelig klausul, den ret instruksjonen tar returadressen fra riktig sted, lokal variabel to.

HopAround: En Java virtuell maskinsimulering

Appleten nedenfor viser en Java-virtuell maskin som utfører en sekvens av bykoder. Bytekodesekvensen i simuleringen ble generert av javac kompilator for hopAround () metoden i klassen vist nedenfor:

klasse Clown {statisk int hopAround () {int i = 0; while (true) {prøv {prøv {i = 1; } til slutt {// den første endelig ledd i = 2; } i = 3; returnere jeg; // dette fullføres aldri, på grunn av fortsett} til slutt {// den andre endelig klausulen hvis (i == 3) {fortsetter; // dette fortsetter tilsidesetter returoppgaven}}}}} 

Bytekodene generert av javac for hopAround () metoden er vist nedenfor:

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