Programmering

Invokedynamic 101

Oracle's Java 7-utgivelse introduserte en ny påkalt dynamisk bytecode-instruksjon til Java Virtual Machine (JVM) og en ny java.lang.invoke API-pakke til standard klassebibliotek. Dette innlegget introduserer deg til denne instruksjonen og API.

Hva og hvordan av påkalt dynamisk

Spørsmål: Hva er påkalt dynamisk?

EN:påkalt dynamisk er en bytecode-instruksjon som letter implementeringen av dynamiske språk (for JVM) gjennom dynamisk metodeinnkalling. Denne instruksjonen er beskrevet i Java SE 7-utgaven av JVM-spesifikasjonen.

Dynamiske og statiske språk

EN dynamisk språk (også kjent som en dynamisk skrevet språk) er et programmeringsspråk på høyt nivå hvis typekontroll vanligvis utføres ved kjøretid, en funksjon kjent som dynamisk skriving. Typekontroll bekrefter at et program er skriv trygt: alle operasjonsargumenter har riktig type. Groovy, Ruby og JavaScript er eksempler på dynamiske språk. (De @ groovy.transform.TypeChecked kommentar får Groovy til å skrive sjekk på kompileringstidspunktet.)

I kontrast, a statisk språk (også kjent som en statisk skrevet språk) utfører typekontroll ved kompileringstid, en funksjon kjent som statisk skriving. Kompilatoren bekrefter at et program er typekorrekt, selv om det kan utsette en slags kontroll til kjøretiden (tenk rollebesetninger og checkcast instruksjon). Java er et eksempel på et statisk språk. Java-kompilatoren bruker denne typen informasjon for å produsere kraftig skrevet bytekode, som kan utføres effektivt av JVM.

Spørsmål: Hvordan gjør påkalt dynamisk legge til rette for dynamisk språkimplementering?

EN: På et dynamisk språk skjer typekontroll vanligvis under kjøretid. Utviklere må passere passende typer eller risikere kjøretidsfeil. Det er ofte slik java.lang.Objekt er den mest nøyaktige typen for et metodeargument. Denne situasjonen kompliserer typekontroll, noe som påvirker ytelsen.

En annen utfordring er at dynamiske språk vanligvis tilbyr muligheten til å legge til felt / metoder til og fjerne dem fra eksisterende klasser. Som et resultat er det nødvendig å utsette klasse, metode og feltoppløsning til kjøretid. Det er også ofte nødvendig å tilpasse en metodeinnkalling til et mål som har en annen signatur.

Disse utfordringene har tradisjonelt krevd at ad hoc-kjøretidsstøtte bygges på toppen av JVM. Denne støtten inkluderer klasser av innpakningstype, bruk av hash-tabeller for å gi dynamisk symboloppløsning, og så videre. Bytecode genereres med inngangspunkter til kjøretiden i form av metodeanrop ved hjelp av en av de fire metodeanropsinstruksjonene:

  • invokestatiske brukes til å påkalle statisk metoder.
  • invokevirtual brukes til å påkalle offentlig og beskyttet ikke-statisk metoder via dynamisk utsendelse.
  • påkalle grensesnitt den er lik invokevirtual bortsett fra at metodeutsendelsen er basert på en grensesnitttype.
  • invokespesial brukes til å påkalle forekomst initialiseringsmetoder (konstruktører) så vel som privat metoder og metoder for en superklasse av dagens klasse.

Denne kjøretidsstøtten påvirker ytelsen. Generert bytecode krever ofte flere faktiske JVM-metodeinnkallinger for en dynamisk språkmetodeanrop. Refleksjon er mye brukt og bidrar til ytelsesforringelse. Dessuten gjør de mange forskjellige utførelsesstiene det umulig for JVMs just-in-time (JIT) kompilator å bruke optimaliseringer.

For å takle dårlig ytelse, er påkalt dynamisk instruksjonen fjerner støtte for ad hoc-kjøretid. I stedet den første samtalen støvelstropper ved å påkalle kjøretidslogikk som effektivt velger en målmetode, og påfølgende anrop påkaller vanligvis målmetoden uten å måtte starte bootstrap på nytt.

påkalt dynamisk gir også dynamiske språkimplementører fordeler ved å støtte dynamisk skiftende mål for anropssiden - a ring nettstedet, nærmere bestemt, a dynamisk anropsside er en påkalt dynamisk instruksjon. Videre fordi JVM støtter internt påkalt dynamisk, denne instruksjonen kan optimaliseres bedre av JIT-kompilatoren.

Metodehåndtak

Spørsmål: det skjønner jeg påkalt dynamisk jobber med metodehåndtak for å legge til rette for dynamisk metodeinnkalling Hva er et metodeshåndtak?

EN: EN metodehåndtak er "en skrevet, direkte kjørbar referanse til en underliggende metode, konstruktør, felt eller lignende operasjon på lavt nivå, med valgfrie transformasjoner av argumenter eller returverdier." Med andre ord, det ligner en C-stil funksjonspeker som peker på kjørbar kode - a mål - og som kan henvises til for å påkalle denne koden. Metodehåndtak er beskrevet abstrakt java.lang.invoke.MethodHandle klasse.

Spørsmål: Kan du gi et enkelt eksempel på metodehåndtering og oppretting?

EN: Sjekk ut oppføring 1.

Oppføring 1. MHD.java (versjon 1)

importere java.lang.invoke.MethodHandle; importere java.lang.invoke.MethodHandles; importere java.lang.invoke.MethodType; public class MHD {public static void main (String [] args) throw Throwable {MethodHandles.Lookup lookup = MethodHandles.lookup (); MethodHandle mh = lookup.findStatic (MHD.class, "hallo", MethodType.methodType (void.class)); mh.invokeExact (); } statisk ugyldig hei () {System.out.println ("hei"); }}

Listing 1 beskriver et demonstrasjonsprogram for metodehåndtak som består av hoved() og Hallo() klassemetoder. Dette programmets mål er å påberope seg Hallo() via et metodeshåndtak.

hoved()første oppgave er å skaffe seg en java.lang.invoke.MethodHandles.Lookup gjenstand. Dette objektet er en fabrikk for å lage metodehåndtak og brukes til å søke etter mål som virtuelle metoder, statiske metoder, spesielle metoder, konstruktører og felttilbehør. Videre er det avhengig av anropssidenes anropssammenheng og håndhever tilgangsbegrensninger for metoden hver gang et metodeshåndtak opprettes. Med andre ord et anropsside (for eksempel oppføring 1 hoved() metode som fungerer som et anropsside) som får et oppslagsobjekt, kan bare få tilgang til de målene som er tilgjengelige for anropssiden. Oppslagsobjektet oppnås ved å påkalle java.lang.invoke.MethodHandles klassen MethodHandles.Lookup lookup () metode.

publicLookup ()

Metode Håndtak erklærer også en MethodHandles.Lookup publicLookup () metode. I motsetning til se opp(), som kan brukes til å skaffe et metodeshåndtak til en hvilken som helst tilgjengelig metode / konstruktør eller felt, publicLookup () kan brukes til å skaffe et metodeshåndtak til et offentlig tilgjengelig felt eller kun offentlig tilgjengelig metode / konstruktør.

Etter å ha oppnådd oppslagsobjektet, er dette objektet MethodHandle findStatic (Class refc, Streng name, MethodType type) metoden kalles for å skaffe et metodeshåndtak til Hallo() metode. Det første argumentet gikk til findStatic () er en referanse til klassen (MHD) hvorfra metoden (Hallo()) er tilgjengelig, og det andre argumentet er metodens navn. Det tredje argumentet er et eksempel på en metodetype, som "representerer argumentene og returtypen som er akseptert og returnert av et metodeshåndtak, eller argumentene og returtypen som er sendt og forventet av en metodehåndteringsanrop." Det er representert av en forekomst av java.lang.invoke.MethodType klasse, og oppnådd (i dette eksemplet) ved å ringe java.lang.invoke.MethodTypes MetodeType metodeType (klasse rtype) metode. Denne metoden kalles fordi Hallo() gir bare en returtype, som tilfeldigvis er tomrom. Denne returtypen gjøres tilgjengelig for methodType () ved å passere ugyldig.klasse til denne metoden.

Det returnerte metodehåndtaket er tildelt mh. Dette objektet blir deretter brukt til å ringe Metode Håndtaks Object invokeExact (Object ... args) metode, for å påkalle metodehåndtaket. Med andre ord, påkalle Eksakt () resulterer i Hallo() blir kalt, og Hallo blir skrevet til standard utgangsstrøm. Fordi påkalle Eksakt () blir erklært å kaste KastbarJeg har lagt til kaster Throwable til hoved() metodehode.

Spørsmål: I ditt forrige svar nevnte du at oppslagsobjektet bare har tilgang til de målene som er tilgjengelige for anropssiden. Kan du gi et eksempel som viser at du prøver å skaffe et metodeshåndtak til et utilgjengelig mål?

EN: Sjekk ut oppføring 2.

Oppføring 2. MHD.java (versjon 2)

importere java.lang.invoke.MethodHandle; importere java.lang.invoke.MethodHandles; importere java.lang.invoke.MethodType; klasse HW {offentlig ugyldig hallo1 () {System.out.println ("hei fra hallo1"); } privat tomrom hello2 () {System.out.println ("hei fra hallo2"); }} public class MHD {public static void main (String [] args) throw Throwable {HW hw = new HW (); MethodHandles.Lookup lookup = MethodHandles.lookup (); MethodHandle mh = lookup.findVirtual (HW.class, "hallo1", MethodType.methodType (void.class)); mh.invoke (hw); mh = lookup.findVirtual (HW.class, "hallo2", MethodType.methodType (void.class)); }}

Oppføring 2 erklærer HW (Hei, Verden) og MHD klasser. HW erklærer en offentlighei1 () eksempel metode og a privathei2 () eksempel metode. MHD erklærer en hoved() metode som vil prøve å påkalle disse metodene.

hoved()Den første oppgaven er å sette i gang HW som forberedelse til påberopning hei1 () og hei2 (). Deretter får den et oppslagsobjekt og bruker dette objektet for å skaffe et metodeshåndtak for å påkalle hei1 (). Denne gangen, MethodHandles.Lookups findVirtual () metoden kalles og det første argumentet som sendes til denne metoden er en Klasse objekt som beskriver HW klasse.

Det viser seg at findVirtual () vil lykkes, og den påfølgende mh.invoke (hw); uttrykk vil påberope seg hei1 (), resulterer i hei fra hei1 blir output.

Fordi hei1 () er offentlig, det er tilgjengelig for hoved() metode anropsside. I motsetning, hei2 () er ikke tilgjengelig. Som et resultat, den andre findVirtual () påkallelse vil mislykkes med en IllegalAccessException.

Når du kjører dette programmet, bør du følge følgende utdata:

hei fra hello1 Unntak i tråden "hoved" java.lang.IllegalAccessException: medlem er privat: HW.hello2 () ugyldig, fra MHD på java.lang.invoke.MemberName.makeAccessException (MemberName.java:507) på java.lang. påkalle.MethodHandles $ Lookup.checkAccess (MethodHandles.java:1172) på java.lang.invoke.MethodHandles $ Lookup.checkMethod (MethodHandles.java:1152) på java.lang.invoke.MethodHandles $ Lookup.accessVirtual (MethodHandles.java: 648) på java.lang.invoke.MethodHandles $ Lookup.findVirtual (MethodHandles.java:641) på MHD.main (MHD.java:27)

Spørsmål: Listing 1 og 2 bruker påkalle Eksakt () og påkalle () metoder for å utføre et metodeshåndtak. Hva er forskjellen mellom disse metodene?

EN: Selv om påkalle Eksakt () og påkalle () er designet for å utføre et metodeshåndtak (faktisk målkoden som metodehåndtaket refererer til), de er forskjellige når det gjelder å utføre typekonvertering på argumenter og returverdien. påkalle Eksakt () utfører ikke automatisk konvertering av kompatibel type på argumenter. Argumentene (eller argumentuttrykkene) må være et nøyaktig samsvar med typesignaturen, med hvert argument gitt separat, eller alle argumentene blir gitt sammen som en matrise. påkalle () krever at argumentene (eller argumentuttrykkene) samsvarer med typekompatibiliteten med metodesignaturen - konverteringer av automatisk type utføres, med hvert argument gitt separat, eller alle argumentene blir gitt sammen som en matrise.

Spørsmål: Kan du gi meg et eksempel som viser hvordan jeg kan påberope et instansfeltets getter og setter?

EN: Sjekk oppføring 3.

Oppføring 3. MHD.java (versjon 3)

importere java.lang.invoke.MethodHandle; importere java.lang.invoke.MethodHandles; importere java.lang.invoke.MethodType; klassepunkt {int x; int y; } offentlig klasse MHD {offentlig statisk ugyldig hoved (String [] args) kaster Throwable {MethodHandles.Lookup lookup = MethodHandles.lookup (); Punktpunkt = nytt punkt (); // Angi feltene x og y. MethodHandle mh = lookup.findSetter (Point.class, "x", int.class); mh.invoke (punkt, 15); mh = lookup.findSetter (Point.class, "y", int.class); mh.invoke (punkt, 30); mh = lookup.findGetter (Point.class, "x", int.class); int x = (int) mh.invoke (punkt); System.out.printf ("x =% d% n", x); mh = lookup.findGetter (Point.class, "y", int.class); int y = (int) mh.invoke (punkt); System.out.printf ("y =% d% n", y); }}

Listing 3 introduserer a Punkt klasse med et par 32-biters heltallsforekomstfelt navngitt x og y. Hvert felt setter og getter er tilgjengelig ved å ringe MethodHandles.Lookups findSetter () og findGetter () metoder, og de resulterende Metode Håndtak blir returnert. Hver av findSetter () og findGetter () krever en Klasse argument som identifiserer feltets klasse, feltets navn og a Klasse objekt som identifiserer feltets signatur.

De påkalle () metoden brukes til å utføre en setter eller getter - bak kulissene får du tilgang til forekomstfeltene via JVMs putfield og getfield bruksanvisning. Denne metoden krever at en referanse til objektet som får tilgang til feltet, blir sendt som det første argumentet. For setterinnkallinger må et annet argument bestått av verdien som tildeles feltet, sendes.

Når du kjører dette programmet, bør du følge følgende utdata:

x = 15 y = 30

Spørsmål: Din definisjon av metodehåndtak inkluderer setningen "med valgfrie transformasjoner av argumenter eller returverdier". Kan du gi et eksempel på argumenttransformasjon?

EN: Jeg har laget et eksempel basert på Matte klassen dobbel pow (dobbel a, dobbel b) klassemetode. I dette eksemplet får jeg et metodeshåndtak til pow () metode, og transformer denne metodehåndtaket slik at det andre argumentet ble sendt til pow () er alltid 10. Sjekk ut oppføring 4.

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