Programiranje

Poglobljeno si oglejte Java Reflection API

V prejšnjem mesecu "Java In-Depth" sem govoril o introspekciji in načinih, kako bi lahko razred Java z dostopom do neobdelanih podatkov razreda pogledal "znotraj" razreda in ugotovil, kako je bil razred zgrajen. Nadalje sem pokazal, da je z dodajanjem nalagalnika razredov te razrede mogoče naložiti v delujoče okolje in izvršiti. Ta primer je oblika statično introspekcija. Ta mesec si bom ogledal Java Reflection API, ki razredi Java omogoča izvedbo dinamično introspekcija: sposobnost pogleda v razrede, ki so že naloženi.

Uporabnost introspekcije

Ena izmed prednosti Java je, da je bila zasnovana ob predpostavki, da se bo okolje, v katerem deluje, dinamično spreminjalo. Razredi se nalagajo dinamično, vezava se izvaja dinamično in primerki predmetov se ustvarjajo dinamično sproti, ko so potrebni. Kar v preteklosti ni bilo zelo dinamično, je sposobnost manipulacije z "anonimnimi" razredi. V tem kontekstu je anonimni razred tisti, ki se v času izvajanja naloži ali predstavi razredu Java in katerega tip program Java še ni bil znan.

Anonimni razredi

Podpiranje anonimnih predavanj je težko razložiti in še težje oblikovati v programu. Izziv podpore anonimnemu razredu lahko navedemo takole: "Napišite program, ki lahko, ko dobi objekt Java, vključi ta predmet v svoje nadaljnje delovanje." Splošna rešitev je precej težka, vendar je z omejevanjem problema mogoče ustvariti nekatere posebne rešitve. V različici Java 1.0 sta dva primera specializiranih rešitev za ta razred težav: programčki Java in različica ukazne vrstice tolmača Java.

Apleti Java so razredi Java, ki jih naloži delujoči navidezni stroj Java v kontekstu spletnega brskalnika in jih prikliče. Ti razredi Java so anonimni, ker čas izvajanja pred časom ne pozna potrebnih informacij za priklic vsakega posameznega razreda. Vendar je težava s klicanjem določenega razreda rešena z uporabo razreda Java java.applet.Applet.

Skupni superrazredi, na primer Appletin vmesniki Java, na primer AppletContext, rešite problem anonimnih predavanj z oblikovanjem predhodno dogovorjene pogodbe. Natančneje, dobavitelj okolja izvajalnega okolja oglašuje, da lahko uporabi kateri koli objekt, ki ustreza določenemu vmesniku, potrošnik okolja izvajalnega okolja pa ta določeni vmesnik uporabi v katerem koli objektu, ki ga namerava zagotoviti v času izvajanja. V primeru programčkov obstaja natančno določen vmesnik v obliki skupnega superrazreda.

Slaba stran običajne rešitve superrazreda, zlasti če ni več dedovanja, je, da objektov, zgrajenih za izvajanje v okolju, ni mogoče uporabiti tudi v kakšnem drugem sistemu, razen če ta sistem ne izvaja celotne pogodbe. V primeru Applet vmesniki, ki jih mora izvajati gostovalno okolje AppletContext. To pomeni za rešitev apleta, da rešitev deluje le, ko naložite aplete. Če postavite primerek datoteke Hashtable predmeta na vaši spletni strani in usmerite brskalnik vanj, se ne bi mogel naložiti, ker sistem programčkov ne more delovati zunaj omejenega obsega.

Poleg primera programčka introspekcija pomaga rešiti težavo, ki sem jo omenil prejšnji mesec: ugotoviti, kako začeti izvajanje v razredu, ki ga je pravkar naložila različica ukazne vrstice navideznega stroja Java. V tem primeru mora navidezni stroj priklicati neko statično metodo v naloženem razredu. Po dogovoru je ta metoda poimenovana glavni in vzame en argument - matriko Vrvica predmetov.

Motivacija za bolj dinamično rešitev

Izziv obstoječe arhitekture Java 1.0 je, da obstajajo težave, ki jih je mogoče rešiti z bolj dinamičnim okoljem za samoogled - na primer naložljive komponente uporabniškega vmesnika, naložljivi gonilniki naprav v OS, ki temelji na Javi, in dinamično nastavljiva okolja za urejanje. "Ubijalska aplikacija" ali težava, zaradi katere je bil ustvarjen Java Reflection API, je bil razvoj objektnega komponentnega modela za Javo. Ta model je zdaj znan kot JavaBeans.

Komponente uporabniškega vmesnika so idealna zasnova za sistem za introspekcijo, saj imajo dva zelo različna potrošnika. Po eni strani so predmeti komponent povezani skupaj, da tvorijo uporabniški vmesnik kot del neke aplikacije. Namesto tega mora obstajati vmesnik za orodja, ki manipulirajo z uporabniškimi komponentami, ne da bi morali vedeti, katere komponente so, ali, kar je še pomembneje, brez dostopa do izvorne kode komponent.

API Java Reflection je zrasel iz potreb API-ja komponente uporabniškega vmesnika JavaBeans.

Kaj je refleksija?

API Reflection je v osnovi sestavljen iz dveh komponent: predmetov, ki predstavljajo različne dele datoteke razreda, in sredstev za varno in varno pridobivanje teh predmetov. Slednje je zelo pomembno, saj Java zagotavlja številne varnostne zaščite, zato ne bi bilo smiselno zagotoviti sklopov razredov, ki bi te zaščitne elemente razveljavili.

Prva komponenta Reflection API je mehanizem, ki se uporablja za pridobivanje informacij o razredu. Ta mehanizem je vgrajen v razred z imenom Razred. Poseben razred Razred je univerzalni tip za meta informacije, ki opisujejo predmete v sistemu Java. Nalagalci razredov v sistemu Java vrnejo predmete tipa Razred. Do zdaj so bile v tem razredu tri najbolj zanimive metode:

  • zaName, ki bi naložil razred z danim imenom z uporabo trenutnega nalagalnika razredov

  • getName, ki bi ime razreda vrnilo kot Vrvica object, ki je bil koristen za prepoznavanje referenc na objekt po imenu razreda

  • newInstance, ki bi poklical ničelni konstruktor razreda (če obstaja) in vam vrnil primerek predmeta tega razreda predmeta

Tem trem uporabnim metodam API Reflection razredu doda nekaj dodatnih metod Razred. Ti so naslednji:

  • getConstructor, getConstructors, getDeclaredConstructor
  • getMethod, getMethods, getDeclaredMethods
  • getField, getFields, getDeclaredFields
  • getSuperclass
  • getInterfaces
  • getDeclaredClasses

Poleg teh metod je bilo dodanih veliko novih razredov, ki predstavljajo predmete, ki bi jih te metode vrnile. Novi razredi so večinoma del programa java.lang.reflect paket, ampak nekateri novi osnovni razredi (Prazno, Bajt, in tako naprej) so v java.lang paket. Odločili smo se, da bomo nove razrede postavili tja, kjer so, tako da bomo v odsevni paket postavili razrede, ki so predstavljali metapodatke, in razrede, ki so predstavljali vrste v jezikovnem paketu.

Tako Reflection API predstavlja številne spremembe razreda Razred ki vam omogočajo, da postavljate vprašanja o notranjih delih predavanja in o številnih predavanjih, ki predstavljajo odgovore na te nove metode.

Kako uporabim API Reflection?

Vprašanje "Kako uporabim API?" je morda bolj zanimivo vprašanje kot "Kaj je razmišljanje?"

Reflection API je simetrično, kar pomeni, da če držite Razred predmet, lahko vprašate o njegovih notranjih delih, in če imate enega od notranjih elementov, ga lahko vprašate, kateri razred ga je razglasil. Tako se lahko premikate naprej in nazaj od razreda do metode do parametra do razreda do metode itd. Ena zanimiva uporaba te tehnologije je ugotoviti večino soodvisnosti med določenim razredom in ostalim sistemom.

Delovni primer

Na bolj praktični ravni pa lahko z API-jem Reflection odstranite razred, podobno kot moj smetišče razred v prejšnjem mesecu.

Za predstavitev API-ja Reflection sem napisal predavanje z imenom ReflectClass ki bi vzel razred, znan v času izvajanja Java (kar pomeni, da je nekje na poti do vašega razreda), in prek API-ja Reflection iztisnil njegovo strukturo v okno terminala. Za eksperimentiranje s tem razredom boste morali imeti na voljo 1.1 različico JDK.

Opomba: Naredite ne poskusite uporabiti čas izvajanja 1.0, saj se zmede, kar običajno povzroči nezdružljivo izjemo pri spremembi razreda.

Razred ReflectClass se začne na naslednji način:

uvoz java.lang.reflect. *; uvoz java.util. *; javni razred ReflectClass { 

Kot lahko vidite zgoraj, prva stvar, ki jo naredi koda, je uvoz razredov API Reflection. Nato skoči naravnost v glavno metodo, ki se začne, kot je prikazano spodaj.

 public static void main (String args []) {Constructor cn []; Razred cc []; Metoda mm []; Polje ff []; Razred c = nič; Razred supClass; Niz x, y, s1, s2, s3; Hashtable classRef = new Hashtable (); if (args.length == 0) {System.out.println ("V ukazni vrstici določite ime razreda."); System.exit (1); } poskusite {c = Class.forName (args [0]); } catch (ClassNotFoundException ee) {System.out.println ("Ni bilo mogoče najti razreda '" + argumentov [0] + "'"); System.exit (1); } 

Metoda glavni izjavlja polja konstruktorjev, polj in metod. Če se spomnite, so to trije od štirih temeljnih delov datoteke predavanj. Četrti del so atributi, do katerih API Reflection žal ne omogoča dostopa. Po nizih sem opravil nekaj obdelave ukazne vrstice. Če je uporabnik vpisal ime razreda, ga koda poskuša naložiti s pomočjo zaName metoda pouka Razred. The zaName metoda zajema imena razredov Java, ne imen datotek, zato se zazremo v java.math.BigInteger razred, preprosto vtipkate "java ReflectClass java.math.BigInteger," namesto da bi opozorili, kje je datoteka razreda dejansko shranjena.

Opredelitev paketa predavanja

Ob predpostavki, da je bila najdena datoteka razreda, koda nadaljuje v korak 0, ki je prikazan spodaj.

 / * * Korak 0: Če naše ime vsebuje pike, smo v paketu, zato najprej dajte *. * / x = c.getName (); y = x.substring (0, x.lastIndexOf (".")); if (y.length ()> 0) {System.out.println ("paket" + y + "; \ n \ r"); } 

V tem koraku se ime razreda pridobi s pomočjo getName metoda pri pouku Razred. Ta metoda vrne popolnoma kvalificirano ime in če ime vsebuje pike, lahko domnevamo, da je bil razred definiran kot del paketa. Korak 0 je torej ločiti del imena paketa od dela imena razreda in natisniti del imena paketa na vrstico, ki se začne s "paket ...."

Zbiranje referenc razredov iz deklaracij in parametrov

Ko je poskrbljeno za izjavo o paketu, nadaljujemo do 1. koraka, ki bo zbral vse drugo imena razredov, na katera se sklicuje ta razred. Ta postopek zbiranja je prikazan v spodnji kodi. Ne pozabite, da so tri najpogostejša mesta, na katera se sklicujejo imena razredov, tipi za polja (spremenljivke primerka), vrnjeni tipi za metode in tipi parametrov, posredovanih metodam in konstruktorjem.

 ff = c.getDeclaredFields (); for (int i = 0; i <ff.length; i ++) {x = tName (ff [i] .getType (). getName (), classRef); } 

V zgornji kodi je polje ff je inicializirano kot matrika Polje predmetov. Zanka zbere ime tipa iz vsakega polja in ga obdela skozi tIme metoda. The tIme metoda je preprost pomočnik, ki vrne okrajšavo imena za tip. Torej java.lang.String postane Vrvica. In v tabeli zapisuje, katere predmete so videli. Na tej stopnji kodo bolj kot tiskanje zanima zbiranje referenc razredov.

Naslednji vir referenc razredov so parametri, dobavljeni konstruktorjem. Naslednji del kode, prikazan spodaj, obdeluje vsak deklarirani konstruktor in zbira reference s seznamov parametrov.

 cn = c.getDeclaredConstructors (); for (int i = 0; i 0) {for (int j = 0; j <cx.length; j ++) {x = tName (cx [j] .getName (), classRef); }}} 

Kot lahko vidite, sem uporabil getParameterTypes metoda v Konstruktor class, da mi posreduje vse parametre, ki jih sprejme določen konstruktor. Ti se nato obdelajo prek tIme metoda.

Zanimivo je omeniti tukaj razliko med metodo getDeclaredConstructors in metodo getConstructors. Obe metodi vrneta vrsto konstruktorjev, vendar getConstructors metoda vrne samo tiste konstruktorje, ki so dostopni vašemu razredu. To je uporabno, če želite vedeti, ali lahko dejansko prikličete konstruktor, ki ste ga našli, vendar za to aplikacijo ni koristno, ker želim natisniti vse konstruktorje v razredu, javni ali ne. Odsevniki polja in metode imajo tudi podobne različice, eno za vse člane in eno samo za javne člane.

Zadnji korak, prikazan spodaj, je zbiranje referenc iz vseh metod. Ta koda mora dobiti reference tako iz vrste metode (podobno kot zgornja polja) kot iz parametrov (podobno kot zgoraj navedeni konstruktorji).

 mm = c.getDeclaredMethods (); for (int i = 0; i 0) {for (int j = 0; j <cx.length; j ++) {x = tName (cx [j] .getName (), classRef); }}} 

V zgornji kodi sta dva klica tIme - eno za zbiranje vrste vrnitve in eno za zbiranje vrste vsakega parametra.

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