Programmering

Invokedynamic 101

Oracle's Java 7-udgivelse introducerede en ny påkaldt dynamisk bytecode-instruktion til Java Virtual Machine (JVM) og en ny java.lang.invoke API-pakke til standardklassebiblioteket. Dette indlæg introducerer dig til denne instruktion og API.

Hvad og hvordan af påkaldt dynamisk

Spørgsmål: Hvad er påkaldt dynamisk?

EN:påkaldt dynamisk er en bytecode-instruktion, der letter implementeringen af ​​dynamiske sprog (for JVM) gennem dynamisk metodeopkald. Denne instruktion er beskrevet i Java SE 7-udgaven af ​​JVM-specifikationen.

Dynamiske og statiske sprog

EN dynamisk sprog (også kendt som en dynamisk skrevet sprog) er et programmeringssprog på højt niveau, hvis typekontrol normalt udføres ved kørsel, en funktion kendt som dynamisk typning. Typekontrol bekræfter, at et program er skriv sikkert: alle funktionsargumenter har den korrekte type. Groovy, Ruby og JavaScript er eksempler på dynamiske sprog. (Det @ groovy.transform.TypeChecked kommentar får Groovy til at skrive kontrol ved kompileringstidspunktet.)

I modsætning hertil a statisk sprog (også kendt som en statisk-skrevet sprog) udfører typekontrol på kompileringstidspunktet, en funktion kendt som statisk typning. Compileren verificerer, at et program er typekorrekt, selvom det kan udskyde en eller anden type kontrol til runtime (tænk rollebesætninger og checkcast instruktion). Java er et eksempel på et statisk sprog. Java-kompilatoren bruger denne typeinformation til at producere stærkt skrevet bytecode, som kan udføres effektivt af JVM.

Spørgsmål: Hvordan gør det? påkaldt dynamisk lette dynamisk sprogimplementering?

EN: På et dynamisk sprog sker typekontrol typisk ved kørsel. Udviklere skal bestå passende typer eller risikere runtime fiaskoer. Det er ofte tilfældet java.lang.Objekt er den mest nøjagtige type for et metodeargument. Denne situation komplicerer typekontrol, hvilket påvirker ydeevnen.

En anden udfordring er, at dynamiske sprog typisk tilbyder muligheden for at tilføje felter / metoder til og fjerne dem fra eksisterende klasser. Som et resultat er det nødvendigt at udsætte klasse, metode og feltopløsning til runtime. Det er også ofte nødvendigt at tilpasse en metodeopkald til et mål, der har en anden signatur.

Disse udfordringer har traditionelt krævet ad hoc-runtime-support, der skal bygges oven på JVM. Denne support inkluderer indpakningstypeklasser, ved hjælp af hash-tabeller til at give dynamisk symbolopløsning osv. Bytecode genereres med indgangspunkter til runtime i form af metodeopkald ved hjælp af en af ​​de fire instruktioner om metodeopkald:

  • invokestatiske bruges til at påberåbe sig statisk metoder.
  • invokevirtual bruges til at påberåbe sig offentlig og beskyttet ikke-statisk metoder via dynamisk forsendelse.
  • påkald grænseflade ligner invokevirtual bortset fra, at metoden afsendelse er baseret på en interface-type.
  • invokespecial bruges til at påberåbe sig initialiseringsmetoder (konstruktører) såvel som privat metoder og metoder til en superklasse af den nuværende klasse.

Denne runtime-support påvirker ydeevnen. Genereret bytecode kræver ofte flere faktiske JVM-metodeopkald til en dynamisk sprogmetodopkald. Refleksion er meget udbredt og bidrager til forringelse af ydeevnen. De mange forskellige eksekveringsstier gør det også umuligt for JVM's just-in-time (JIT) compiler at anvende optimeringer.

For at imødegå dårlig præstation, påkaldt dynamisk instruktion fjerner ad hoc runtime support. I stedet for det første opkald bootstraps ved at påkalde runtime-logik, der effektivt vælger en målmetode, og efterfølgende opkald påberåber sig typisk målmetoden uden at skulle genstarte.

påkaldt dynamisk gavner også dynamiske sprogimplementatorer ved at understøtte dynamisk skiftende mål for opkaldswebsteder - a ring websted, mere specifikt, a dynamisk opkaldssted er en påkaldt dynamisk instruktion. Desuden fordi JVM understøtter internt påkaldt dynamisk, kan denne instruktion optimeres bedre af JIT-kompilatoren.

Metodehåndtag

Spørgsmål: Jeg forstår, at påkaldt dynamisk arbejder med metodehåndtag for at lette dynamisk metodeopkald. Hvad er et metodeshåndtag?

EN: EN metodehåndtag er "en skrevet, direkte eksekverbar henvisning til en underliggende metode, konstruktør, felt eller lignende operation på lavt niveau med valgfri transformation af argumenter eller returværdier." Med andre ord ligner det en C-stil-funktionsmarkør, der peger på eksekverbar kode - a mål - og som kan henvises til for at påberåbe sig denne kode. Metodehåndtag beskrives abstrakt java.lang.invoke.MethodHandle klasse.

Spørgsmål: Kan du give et simpelt eksempel på metodehåndtering af oprettelse og påkaldelse?

EN: Tjek Listing 1.

Liste 1. MHD.java (version 1)

importere java.lang.invoke.MethodHandle; importere java.lang.invoke.MethodHandles; import 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, "hej", MethodType.methodType (void.class)); mh.invokeExact (); } statisk ugyldigt hej () {System.out.println ("hej"); }}

Liste 1 beskriver et demonstrationsprogram til håndteringsmetoder, der består af hoved () og Hej() klassemetoder. Dette programs mål er at påberåbe sig Hej() via et metodehåndtag.

hoved ()'s første opgave er at skaffe en java.lang.invoke.MethodHandles.Lookup objekt. Dette objekt er en fabrik til oprettelse af metodehåndtag og bruges til at søge efter mål såsom virtuelle metoder, statiske metoder, specielle metoder, konstruktører og feltadgang. Desuden er det afhængigt af et opkaldswebbs opfordringskontekst og håndhæver begrænsninger for adgang til metoder til håndtering hver gang der oprettes et metodeshåndtag. Med andre ord et opkaldsside (såsom lister 1 hoved () metode, der fungerer som et opkaldssted), der opnår et opslagsobjekt, kan kun få adgang til de mål, der er tilgængelige for opkaldsstedet. Opslagsobjektet opnås ved at påberåbe sig java.lang.invoke.MethodHandles klassens MethodHandles.Lookup opslag () metode.

publicLookup ()

Metode Håndtag erklærer også en MethodHandles.Lookup publicLookup () metode. I modsætning til kig op(), som kan bruges til at opnå et metodeshåndtag til enhver tilgængelig metode / konstruktør eller felt, publicLookup () kan bruges til at opnå et metodeshåndtag til et offentligt tilgængeligt felt eller kun offentligt tilgængelig metode / konstruktør.

Efter at have opnået opslagsobjektet, er dette objekt MetodeHåndtag findStatic (Class refc, String name, MethodType type) metoden kaldes for at opnå et metodeshåndtag til Hej() metode. Det første argument blev sendt til findStatic () er en henvisning til klassen (MHD) hvorfra metoden (Hej()) er adgang til, og det andet argument er metodens navn. Det tredje argument er et eksempel på en metode type, som "repræsenterer argumenterne og returneringstypen, der er accepteret og returneret af et metodeshåndtag, eller de argumenter og returtype, der er sendt og forventet af en metodehåndtereropkald." Det er repræsenteret af en forekomst af java.lang.invoke.MethodType klasse og opnået (i dette eksempel) ved at ringe java.lang.invoke.MethodType's MetodeType metodeType (klasse rtype) metode. Denne metode kaldes fordi Hej() giver kun en returtype, som tilfældigvis er ugyldig. Denne returtype stilles til rådighed for methodType () ved at passere ugyldig.klasse til denne metode.

Det returnerede metodehåndtag er tildelt mh. Dette objekt bruges derefter til at ringe Metode Håndtag's Object invokeExact (Object ... args) metode for at påberåbe sig håndteringen af ​​metoden. Med andre ord, påkalde Eksakt () resulterer i Hej() bliver kaldt, og Hej bliver skrevet til standardudgangsstrømmen. Fordi påkaldExact () erklæres at kaste Kan kastes, Jeg har tilføjet kaster kastbar til hoved () metodehoved.

Spørgsmål: I dit tidligere svar nævnte du, at opslagsobjektet kun kan få adgang til de mål, der er tilgængelige for opkaldssiden. Kan du give et eksempel, der demonstrerer forsøg på at få et metodeshåndtag til et utilgængeligt mål?

EN: Tjek Listing 2.

Liste 2. MHD.java (version 2)

importere java.lang.invoke.MethodHandle; importere java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; klasse HW {public void hello1 () {System.out.println ("hej fra hello1"); } privat tomrum hello2 () {System.out.println ("hej fra hello2"); }} offentlig klasse MHD {offentlig statisk ugyldig hoved (String [] args) kaster Throwable {HW hw = new HW (); MethodHandles.Lookup lookup = MethodHandles.lookup (); MethodHandle mh = lookup.findVirtual (HW.class, "hej1", MethodType.methodType (void.class)); mh.invoke (hw); mh = lookup.findVirtual (HW.class, "hello2", MethodType.methodType (void.class)); }}

Liste 2 erklærer HW (Hej, Verden) og MHD klasser. HW erklærer en offentlighej1 () eksempel metode og a privathej2 () eksempel metode. MHD erklærer en hoved () metode, der forsøger at påberåbe sig disse metoder.

hoved ()Første opgave er at instantiere HW som forberedelse til påberåbelse hej1 () og hej2 (). Derefter opnår det et opslagsobjekt og bruger dette objekt til at opnå et metodehåndtag til påberåbelse hej1 (). Denne gang, MethodHandles.Lookup's findVirtual () metode kaldes, og det første argument, der sendes til denne metode, er en Klasse objekt, der beskriver HW klasse.

Det viser sig at findVirtual () vil lykkes, og den efterfølgende mh.invoke (hw); udtryk vil påberåbe sig hej1 (), resulterende i hej fra hej1 bliver output.

Fordi hej1 () er offentlig, det er tilgængeligt for hoved () metode opkaldssted. I modsætning, hej2 () er ikke tilgængelig. Som et resultat, den anden findVirtual () påkaldelse mislykkes med en Ulovlig adgangsundtagelse.

Når du kører denne applikation, skal du overholde følgende output:

hej fra hello1 Undtagelse i tråden "main" java.lang.IllegalAccessException: medlem er privat: HW.hello2 () ugyldig, fra MHD på java.lang.invoke.MemberName.makeAccessException (MemberName.java:507) på java.lang. påkalde.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ørgsmål: Liste 1 og 2 bruger påkaldExact () og påberåbe sig () metoder til at udføre et metodeshåndtag. Hvad er forskellen mellem disse metoder?

EN: Selvom påkaldExact () og påberåbe sig () er designet til at udføre et metodeshåndtag (faktisk den målkode, som metodehåndtaget refererer til), de adskiller sig, når det kommer til at udføre typekonverteringer på argumenter og returværdien. påkaldExact () udfører ikke automatisk kompatibel konvertering af argumenter. Dens argumenter (eller argumentudtryk) skal være en nøjagtig typematch til metodesignaturen, hvor hvert argument gives separat, eller alle argumenter leveres sammen som en matrix. påberåbe sig () kræver, at dets argumenter (eller argumentudtryk) er et typekompatibelt match med metodesignaturen - automatisk typekonvertering udføres, hvor hvert argument leveres separat, eller alle argumenter leveres sammen som en matrix.

Spørgsmål: Kan du give mig et eksempel, der viser, hvordan jeg påberåber et instansfelts getter og setter?

EN: Tjek Listing 3.

Liste 3. MHD.java (version 3)

importere java.lang.invoke.MethodHandle; importere java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; klasse Punkt {int x; int y; } offentlig klasse MHD {offentlig statisk ugyldig hoved (String [] args) kaster Throwable {MethodHandles.Lookup lookup = MethodHandles.lookup (); Punktpunkt = nyt punkt (); // Indstil felterne 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 introducerer a Punkt klasse med et par 32-bit heltal-instansfelter navngivet x og y. Hvert felt setter og getter fås ved at ringe MethodHandles.Lookup's findSetter () og findGetter () metoder og de deraf følgende Metode Håndtag returneres. Hver af findSetter () og findGetter () kræver en Klasse argument, der identificerer feltets klasse, feltets navn og a Klasse objekt, der identificerer feltets signatur.

Det påkalde () metode bruges til at udføre en setter eller getter - bag kulisserne er der adgang til instansfelterne via JVM's putfield og getfield instruktioner. Denne metode kræver, at en reference til det objekt, hvis felt der er adgang til, videregives som det indledende argument. For setteropkald skal et andet argument bestående af den værdi, der tildeles feltet, også videregives.

Når du kører denne applikation, skal du overholde følgende output:

x = 15 y = 30

Spørgsmål: Din definition af metodehåndtag inkluderer sætningen "med valgfri transformation af argumenter eller returværdier". Kan du give et eksempel på argumenttransformation?

EN: Jeg har oprettet et eksempel baseret på Matematik klassens dobbelt pow (dobbelt a, dobbelt b) klassemetode. I dette eksempel får jeg et metodehåndtag til pow () metode, og transformer denne metodehåndtag, så det andet argument sendes til pow () er altid 10. Tjek Listing 4.