Programavimas

„Invokedynamic 101“

„Oracle“ leidimas „Java 7“ pristatė naują iškviestas dinamiškas baitų kodavimo instrukcija „Java Virtual Machine“ (JVM) ir nauja java.lang.invoke API paketas į standartinę klasės biblioteką. Šis įrašas supažindina jus su šia instrukcija ir API.

Kas ir kaip iškviesta dinamika

Klausimas: Kas yra iškviestas dinamiškas?

A:iškviestas dinamiškas yra baito kodo instrukcija, kuri palengvina dinaminių kalbų (JVM) diegimą naudojant dinaminį metodo iškvietimą. Ši instrukcija aprašyta JVM specifikacijos „Java SE 7“ leidime.

Dinaminės ir statinės kalbos

A dinamiška kalba (taip pat žinomas kaip a dinamiškai spausdinta kalba) yra aukšto lygio programavimo kalba, kurios tipo tikrinimas paprastai atliekamas vykdymo metu, funkcija, žinoma kaip dinaminis spausdinimas. Tipo patikrinimas patikrina, ar programa yra tipo seifas: visi operacijos argumentai turi teisingą tipą. „Groovy“, „Ruby“ ir „JavaScript“ yra dinamiškų kalbų pavyzdžiai. ( @ groovy.transform.TyChecked anotacija sukelia „Groovy“ tipo patikrinimą kompiliavimo metu.)

Priešingai, a statinė kalba (taip pat žinomas kaip a statiškai įvesta kalba) atlieka tipo patikrinimą kompiliavimo metu, tai funkcija vadinama statinis spausdinimas. Kompiliatorius patikrina, ar programos tipas yra teisingas, nors kai kurių tipų tikrinimą gali atidėti vykdymo laikas („Think Cast“ ir čekis instrukcija). „Java“ yra statinės kalbos pavyzdys. „Java“ kompiliatorius naudoja šio tipo informaciją, kad sukurtų labai tipizuotą baitų kodą, kurį JVM gali efektyviai vykdyti.

Klausimas: Kaip iškviestas dinamiškas palengvinti dinamišką kalbos diegimą?

A: Dinamiškoje kalboje tipo tikrinimas paprastai atliekamas vykdymo metu. Kūrėjai turi išlaikyti tinkamus tipus arba rizikuoti vykdymo metu. Dažnai taip būna java.lang.Object yra tiksliausias metodo argumento tipas. Ši situacija apsunkina tipo patikrinimą, o tai daro įtaką našumui.

Kitas iššūkis yra tas, kad dinaminės kalbos paprastai suteikia galimybę pridėti laukus / metodus ir pašalinti juos iš esamų klasių. Todėl būtina atidėti klasės, metodo ir lauko skiriamąją gebą vykdymui. Be to, dažnai reikia pritaikyti metodo iškvietimą tikslui, turinčiam kitokį parašą.

Dėl šių iššūkių tradiciškai reikėjo ad hoc vykdymo laiko palaikymo sukurti ant JVM. Ši parama apima įvyniojimo tipo klases, maišos lentelių naudojimą dinaminei simbolių raiškai užtikrinti ir pan. „Bytecode“ generuojamas su įvesties taškais į vykdymo laiką metodų iškvietimų forma, naudojant bet kurią iš keturių metodo iškvietimo instrukcijų:

  • invokestatic yra naudojamas iškviesti statinis metodai.
  • invokevirtualus yra naudojamas iškviesti visuomenės ir saugomi nestatinis metodus per dinaminį išsiuntimą.
  • invokeinterface yra panašus į invokevirtualus išskyrus metodo siuntimą, pagrįstą sąsajos tipu.
  • remiasi specialiuoju yra naudojamas ir egzempliorių inicijavimo metodams (konstruktoriams), ir privatus dabartinės klasės superklasės metodai ir metodai.

Ši vykdymo laiko parama turi įtakos našumui. Generuojamam baitkodui dažnai reikia kelių faktinių JVM metodo iškvietimų vienam dinaminio kalbos metodo iškvietimui. Refleksija yra plačiai naudojama ir prisideda prie veiklos pablogėjimo. Be to, dėl daugybės skirtingų vykdymo kelių JVM „just-in-time“ (JIT) kompiliatorius negali pritaikyti optimizavimo.

Siekdama išspręsti blogus rezultatus, iškviestas dinamiškas instrukcijos panaikina ad hoc vykdymo laiko palaikymą. Vietoj to, pirmasis skambutis bagažinės pasinaudojant vykdymo laiko logika, kuri efektyviai parenka tikslinį metodą, o paskesni skambučiai paprastai iškviečia tikslinį metodą nereikalaujant iš naujo paleisti.

iškviestas dinamiškas taip pat naudinga dinamiškų kalbų diegėjams, palaikydama dinamiškai besikeičiančius skambučių svetainės tikslus - a skambučio svetainė, tiksliau, a dinaminio skambučio svetainė yra iškviestas dinamiškas instrukcija. Be to, todėl, kad JVM palaiko viduje iškviestas dinamiškas, šią instrukciją gali geriau optimizuoti JIT kompiliatorius.

Metodo rankenos

Klausimas: Aš suprantu tai iškviestas dinamiškas veikia su metodo rankenomis, kad palengvintų dinamišką metodo iškvietimą. Kas yra metodo rankena?

A: A metodo rankena yra „tipinė, tiesiogiai vykdoma nuoroda į pagrindinį metodą, konstruktorių, lauką ar panašią žemo lygio operaciją su pasirinktinėmis argumentų ar grąžinimo verčių transformacijomis“. Kitaip tariant, tai panašu į C stiliaus funkcijų rodyklę, kuri nurodo vykdomąjį kodą - a taikinys - ir kuriai negalima daryti nuorodos į šį kodą. Metodo rankenas apibūdina santrauka java.lang.invoke.MethodHandle klasė.

Klausimas: Ar galite pateikti paprastą metodo tvarkymo ir iškvietimo pavyzdį?

A: Peržiūrėkite 1 sąrašą.

1 sąrašas. MHD.java (1 versija)

importuoti java.lang.invoke.MethodHandle; importuoti java.lang.invoke.MethodHandles; importuoti java.lang.invoke.MethodType; public class MHD {public static void main (String [] argumentai) meta Throwable {MethodHandles.Lookup lookup = MethodHandles.lookup (); MethodHandle mh = lookup.findStatic (MHD.class, "labas", MethodType.methodType (void.class)); mh.invokeExact (); } static void labas () {System.out.println ("labas"); }}

1 sąraše aprašoma metodo rankenos demonstravimo programa, susidedanti iš pagrindinis () ir Sveiki() klasės metodai. Šios programos tikslas yra pasinaudoti Sveiki() per metodo rankeną.

pagrindinis ()Pirmoji užduotis yra gauti java.lang.invoke.MethodHandles.Lookup objektas. Šis objektas yra metodas, skirtas rankenoms kurti, ir naudojamas tikslams, tokiems kaip virtualūs metodai, statiniai metodai, specialieji metodai, konstruktoriai ir lauko prieigos priemonės, ieškoti. Be to, tai priklauso nuo skambučių svetainės iškvietimo konteksto ir taiko metodo tvarkymo prieigos apribojimus kiekvieną kartą, kai sukuriama metodo rankena. Kitaip tariant, skambučių svetainė (pvz., 1 sąrašas pagrindinis () metodas, veikiantis kaip skambučio svetainė), kuris gauna paieškos objektą, gali pasiekti tik tuos tikslus, kurie yra prieinami skambučio svetainei. Paieškos objektas gaunamas iškviečiant java.lang.invoke.MethodHandles klasės MethodHandles.Lookup lookup () metodas.

publicLookup ()

MetodasRankos taip pat deklaruoja a MethodHandles.Lookup publicLookup () metodas. Skirtingai ieškoti (), kuri gali būti naudojama norint gauti metodo rankeną prieinamam metodui / konstruktoriui ar laukui, publicLookup () gali būti naudojamas norint gauti metodo rankeną tik viešai prieinamam laukui arba viešai prieinamam metodui / konstruktoriui.

Gavę paieškos objektą, šis objektas „MethodHandle findStatic“ (klasės nuorodos, eilutės pavadinimas, „MethodType“ tipas) metodas yra vadinamas norint gauti metodo rankeną Sveiki() metodas. Pirmasis argumentas perduotas „findStatic“ () yra nuoroda į klasę (MHD), iš kurio metodas (Sveiki()), o antrasis argumentas yra metodo pavadinimas. Trečias argumentas yra a pavyzdys metodo tipas, kuris „reiškia argumentus ir grąžinimo tipą, kurį priėmė ir grąžino metodo rankena, arba argumentus ir grąžinimo tipą, kuriuos perdavė ir tikėjosi metodo rankenos skambintojas“. Tai atstovauja java.lang.invoke.MethodType klasę, ir gautas (šiame pavyzdyje) paskambinus java.lang.invoke.MethodType's MethodType methodType (klasės rtype) metodas. Šis metodas vadinamas todėl Sveiki() pateikia tik grąžinimo tipą, kuris būna tuštuma. Šis grąžinimo tipas yra prieinamas methodType () praeinant tuštuma.klasė prie šio metodo.

Grąžinta metodo rankena priskiriama mh. Šis objektas naudojamas paskambinti MethodHandle's Object invokeExact (objektas ... argumentai) metodas, pasinaudoti metodo rankena. Kitaip tariant, invokeExact () rezultatai Sveiki() skambinama ir Sveiki rašoma į standartinį išvesties srautą. Nes invokeExact () deklaruojama mesti Metamas, Aš pridėjau metimai Metami į pagrindinis () metodo antraštė.

Klausimas: Ankstesniame atsakyme minėjote, kad paieškos objektas gali pasiekti tik tuos tikslus, kurie yra prieinami skambučio svetainei. Ar galite pateikti pavyzdį, parodantį bandymą pasiekti metodo rankeną nepasiekiamam taikiniui?

A: Peržiūrėkite 2 sąrašą.

2 sąrašas. MHD.java (2 versija)

importuoti java.lang.invoke.MethodHandle; importuoti java.lang.invoke.MethodHandles; importuoti java.lang.invoke.MethodType; klasė HW {public void hello1 () {System.out.println ("labas nuo labo1"); } private void labas2 () {System.out.println ("labas nuo labo2"); }} public class MHD {public static void main (String [] args) metimai Metamas {HW hw = new HW (); MethodHandles.Lookup lookup = MethodHandles.lookup (); MethodHandle mh = lookup.findVirtual (HW.class, "hello1", MethodType.methodType (void.class)); mh.invoke (hw); mh = lookup.findVirtual (HW.class, "hello2", MethodType.methodType (void.class)); }}

2 sąrašas skelbia HW (Sveiki, pasauli) ir MHD klasės. HW pareiškia a visuomenėslabas1 () egzemplioriaus metodas ir a privatuslabas2 () egzemplioriaus metodas. MHD pareiškia a pagrindinis () metodas, kuris bandys pasinaudoti šiais metodais.

pagrindinis ()Pirmoji užduotis yra akimirksniu HW rengiantis iškviesti labas1 () ir labas2 (). Tada jis gauna paieškos objektą ir naudoja šį objektą, kad gautų metodo rankeną iškvietimui labas1 (). Šį kartą, MethodHandles.Lookup's findVirtual () metodas yra iškviečiamas ir pirmasis šiam metodui perduotas argumentas yra a Klasė objektas, apibūdinantis HW klasė.

Paaiškėjo, kad „findVirtual“ () pavyks, ir paskesni mh.invoke (hw); išraiška sukvies labas1 (), kurio rezultatas labas nuo labo1 yra išvestis.

Nes labas1 () yra visuomenės, jis prieinamas pagrindinis () metodo skambučio svetainė. Priešingai, labas2 () nėra pasiekiamas. Dėl to antrasis „findVirtual“ () iškvietimas nepavyks naudojant „IllegalAccessException“.

Kai paleisite šią programą, turėtumėte stebėti šį išvestį:

labas nuo hello1 Išimtis temoje "main" java.lang.IllegalAccessException: narys yra privatus: HW.hello2 () void, iš MHD adresu java.lang.invoke.MemberName.makeAccessException (MemberName.java:507) java.lang. invoke.MethodHandles $ Lookup.checkAccess (MethodHandles.java:1172) adresu java.lang.invoke.MethodHandles $ Lookup.checkMethod (MethodHandles.java:1152) adresu java.lang.invoke.MethodHandles $ Lookup.accessVirtual (Method: 648) adresu java.lang.invoke.MethodHandles $ Lookup.findVirtual (MethodHandles.java:641) MHD.main (MHD.java:27)

Klausimas: 1 ir 2 sąrašuose naudojamas invokeExact () ir iškviesti () metodai atlikti metodo rankeną. Kuo skiriasi šie metodai?

A: Nors invokeExact () ir iškviesti () yra skirti atlikti metodo rankenėlę (iš tikrųjų tikslinį kodą, į kurį nurodo metodo rankena), jie skiriasi, kai reikia atlikti tipo konversijas pagal argumentus ir grąžinimo vertę. invokeExact () neatlieka automatinio suderinamo tipo konversijos argumentuose. Jos argumentai (arba argumentų išraiškos) turi tiksliai atitikti metodo parašą, kiekvienas argumentas pateikiamas atskirai arba visi argumentai pateikiami kartu kaip masyvas. iškviesti () reikalauja, kad jo argumentai (arba argumentų išraiškos) atitiktų tipą, suderinamą su metodo parašu - atliekamos automatinės tipo konversijos, kiekvienam argumentui pateikiant atskirai arba visiems argumentams kartu pateikiant masyvą.

Klausimas: Ar galite pateikti pavyzdį, kuris parodo, kaip iškviesti egzemplioriaus lauko žymiklį ir seterį?

A: Peržiūrėkite 3 sąrašą.

3 sąrašas. MHD.java (3 versija)

importuoti java.lang.invoke.MethodHandle; importuoti java.lang.invoke.MethodHandles; importuoti java.lang.invoke.MethodType; klasė Taškas {int x; int y; } public class MHD {public static void main (String [] argumentai) meta Throwable {MethodHandles.Lookup lookup = MethodHandles.lookup (); Taškinis taškas = naujas taškas (); // Nustatykite x ir y laukus. MethodHandle mh = lookup.findSetter (Point.class, "x", int.class); mh.invoke (15 punktas); mh = lookup.findSetter (taškas. klasė, „y“, vid. klasė); mh.invoke (30 punktas); mh = lookup.findGetter (taško klasė, „x“, vidinė klasė); int x = (int) mh. iškvietimas (taškas); System.out.printf ("x =% d% n", x); mh = lookup.findGetter (taškas. klasė, „y“, vid. klasė); int y = (int) mh. iškvietimas (taškas); System.out.printf ("y =% d% n", y); }}

3 sąraše pateikiama a Taškas klasė su 32 bitų sveikojo skaičiaus egzempliorių laukų pavadinimu x ir y. Kiekvieno lauko nustatiklį ir parametrą galima pasiekti paskambinus MethodHandles.Lookup's „findSetter“ () ir „findGetter“ () metodus ir gautus MethodHandle yra grąžinamas. Kiekvienas iš „findSetter“ () ir „findGetter“ () reikalauja a Klasė argumentas, nurodantis lauko klasę, lauko pavadinimą ir a Klasė objektas, identifikuojantis lauko parašą.

iškviesti () metodas naudojamas vykdant seterį ar „getter“ - užkulisiuose egzempliorių laukai pasiekiami per JVM putfieldas ir getfield instrukcijas. Šis metodas reikalauja, kad nuoroda į objektą, kurio lauką pasiekiama, būtų perduodama kaip pradinis argumentas. Norint nustatyti seterių iškvietimus, taip pat reikia perduoti antrą argumentą, susidedantį iš laukui priskiriamos vertės.

Kai paleisite šią programą, turėtumėte stebėti šį išvestį:

x = 15 y = 30

Klausimas: Metodo rankenos apibrėžime yra frazė „su pasirinktinėmis argumentų arba grąžinimo reikšmių transformacijomis“. Ar galite pateikti argumentų transformavimo pavyzdį?

A: Aš sukūriau pavyzdį, pagrįstą Matematika klasės dviguba galia (dviguba a, dviguba b) klasės metodas. Šiame pavyzdyje aš gaunu metodo rankeną Pow () metodą ir transformuokite šio metodo rankeną taip, kad antrasis argumentas būtų perduotas Pow () yra visada 10. Peržiūrėkite 4 sąrašą.