Programavimas

Išsamiai pažvelkite į „Java Reflection“ API

Praėjusio mėnesio leidinyje „Java In-Depth“ kalbėjau apie savistabą ir būdus, kaip „Java“ klasė, turinti prieigą prie neapdorotų klasės duomenų, galėtų atrodyti „viduje“ ir suprasti, kaip klasė buvo sukurta. Be to, aš parodžiau, kad pridėjus klasės krautuvą, tas klases galima įkelti į bėgimo aplinką ir vykdyti. Šis pavyzdys yra statinis savistaba. Šį mėnesį apžvelgsiu „Java Reflection“ API, kuri suteikia „Java“ klasėms galimybę atlikti dinamiškas savistaba: galimybė pažvelgti į jau pakrautų klasių vidų.

Savistabos naudingumas

Viena iš „Java“ stipriųjų pusių yra ta, kad ji buvo sukurta darant prielaidą, kad aplinka, kurioje ji veikė, keisis dinamiškai. Klasės yra dinamiškai įkeltos, susiejimas atliekamas dinamiškai, o objektų egzemplioriai dinamiškai kuriami skriejant, kai jų reikia. Istoriškai nebuvo labai dinamiška - tai galimybė manipuliuoti „anoniminėmis“ klasėmis. Šiame kontekste anoniminė klasė yra klasė, kuri yra įkeliama arba pateikiama „Java“ klasei vykdymo metu ir kurios tipas anksčiau nebuvo žinomas „Java“ programai.

Anoniminės klasės

Anoniminių klasių palaikymą sunku paaiškinti, o programoje - dar sunkiau. Anoniminės klasės palaikymo iššūkis gali būti toks: "Parašykite programą, kuri, gavusi" Java "objektą, gali įtraukti tą objektą į tolesnį jo veikimą." Bendras sprendimas yra gana sunkus, tačiau suvaržžius problemą galima sukurti keletą specializuotų sprendimų. Yra du specializuotų šios klasės problemų sprendimų „Java“ 1.0 versijoje pavyzdžiai: „Java“ programėlės ir „Java“ vertėjo komandinės eilutės versija.

„Java“ programėlės yra „Java“ klasės, kurias įkelia veikianti „Java“ virtualioji mašina žiniatinklio naršyklės kontekste ir kurios iškviečiamos. Šios „Java“ klasės yra anoniminės, nes vykdymo laikas iš anksto nežino reikalingos informacijos, kad būtų galima pakviesti kiekvieną atskirą klasę. Tačiau tam tikros klasės iškvietimo problema išspręsta naudojant „Java“ klasę java.applet.Applet.

Paprastos superklasės, pvz Appletir „Java“ sąsajos „AppletContext“, spręskite anoniminių klasių problemą sukurdami anksčiau sutartą sutartį. Konkrečiai, vykdymo laiko aplinkos tiekėja reklamuoja, kad gali naudoti bet kurį objektą, kuris atitinka nurodytą sąsają, o vykdymo laiko aplinkos vartotojas naudoja tą nurodytą sąsają bet kuriame objekte, kurį jis ketina pateikti vykdymo laikui. Programėlių atveju yra tiksliai nurodyta sąsaja bendro superklasės pavidalu.

Bendro superklasės sprendimo minusas, ypač jei nėra daugybinio paveldėjimo, yra tai, kad objektai, pastatyti veikti aplinkoje, taip pat negali būti naudojami kitoje sistemoje, nebent ta sistema įgyvendina visą sutartį. Dėl Applet sąsajas, turi būti įdiegta prieglobos aplinka „AppletContext“. Tai reiškia programėlių sprendimui, kad sprendimas veikia tik tada, kai kraunate programėles. Jei įdėsite a „Hashtable“ objekto jūsų tinklalapyje ir nukreipkite į jį naršyklę, jo nepavyks įkelti, nes programėlių sistema negali veikti už riboto diapazono ribų.

Be programėlių pavyzdžio, savistaba padeda išspręsti praeitą mėnesį minėtą problemą: išsiaiškinti, kaip pradėti vykdyti klasę, kurią ką tik įkėlė „Java“ virtualiosios mašinos komandinės eilutės versija. Šiame pavyzdyje virtualioji mašina turi naudoti tam tikrą statinį metodą pakrautoje klasėje. Pagal susitarimą tas metodas yra pavadintas pagrindinis ir paima vieną argumentą - masyvą Stygos objektai.

Dinamiškesnio sprendimo motyvacija

Esamos „Java 1.0“ architektūros iššūkis yra tas, kad yra problemų, kurias galėtų išspręsti dinamiškesnė savianalizės aplinka, pvz., Įkeliami vartotojo sąsajos komponentai, įkeliami įrenginių tvarkyklės „Java“ pagrindinėje OS ir dinamiškai konfigūruojamos redagavimo aplinkos. „Programa žudikė“ arba problema, dėl kurios buvo sukurta „Java Reflection“ API, buvo „Java“ objekto komponento modelio kūrimas. Tas modelis dabar žinomas kaip „JavaBeans“.

Vartotojo sąsajos komponentai yra idealus introspekcinės sistemos projektavimo taškas, nes jie turi du labai skirtingus vartotojus. Viena vertus, komponentų objektai yra susieti, kad būtų sukurta vartotojo sąsaja kaip kurios nors programos dalis. Arba reikia turėti sąsają įrankiams, kurie valdo vartotojo komponentus nežinodami, kas yra komponentai, arba, dar svarbiau, neturėdami prieigos prie komponentų šaltinio kodo.

„Java Reflection“ API išaugo iš „JavaBeans“ vartotojo sąsajos komponento API poreikių.

Kas yra atspindys?

Iš esmės „Reflection“ API sudaro du komponentai: objektai, vaizduojantys įvairias klasės failo dalis, ir priemonė, leidžianti saugiai ir saugiai išgauti tuos objektus. Pastarasis yra labai svarbus, nes „Java“ suteikia daugybę apsaugos priemonių, ir nebūtų prasmės pateikti klasių rinkinį, kuris šias apsaugos priemones panaikino.

Pirmasis „Reflection“ API komponentas yra mechanizmas, naudojamas gaunant informaciją apie klasę. Šis mechanizmas yra įmontuotas įvardytoje klasėje Klasė. Specialioji klasė Klasė yra universalus meta informacijos tipas, apibūdinantis „Java“ sistemos objektus. Klasių krautuvai „Java“ sistemoje grąžina tipo objektus Klasė. Iki šiol trys įdomiausi šios klasės metodai buvo:

  • forName, kuris įkeltų vardo klasę naudodamas dabartinį klasės krautuvą

  • getName, kuris grąžintų klasės pavadinimą kaip a Stygos objektas, kuris buvo naudingas norint identifikuoti objekto nuorodas pagal jų klasės pavadinimą

  • newInstance, kuris pakviestų nulinį konstruktorių klasei (jei ji yra) ir grąžintų jums tos klasės objektų objekto egzempliorių

Prie šių trijų naudingų metodų „Reflection“ API prideda keletą papildomų metodų Klasė. Tai yra šie:

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

Be šių metodų, buvo pridėta daug naujų klasių, vaizduojančių objektus, kuriuos šie metodai grąžins. Naujos klasės dažniausiai yra „Kursų“ dalis java.lang.reflect paketą, tačiau kai kurios naujos pagrindinio tipo klasės (Tuštuma, Baitasir pan.) yra java.lang pakuotė. Naujus užsiėmimus buvo nuspręsta pritaikyti ten, kur jie yra, metaduomenis reprezentuojančias klases įtraukiant į refleksijos paketą, o klases, kurios tipus - kalbos pakete.

Taigi „Reflection“ API rodo daugybę klasės pakeitimų Klasė tai leidžia užduoti klausimus apie klasės vidų ir daugybę klasių, kurios atspindi atsakymus, kuriuos jums suteikia šie nauji metodai.

Kaip naudoti „Reflection“ API?

Klausimas "Kaip naudoti API?" galbūt yra įdomesnis klausimas nei „Kas yra atspindys?“

„Reflection“ API yra simetriškas, o tai reiškia, kad jei turite a Klasė objekto, galite paklausti apie jo vidų, o jei turite vieną iš vidinių, galite paklausti, kuri klasė jį deklaravo. Taigi jūs galite judėti pirmyn ir atgal iš klasės į metodą į parametrą į klasę ir pan. Vienas įdomus šios technologijos panaudojimas yra išsiaiškinti daugumą tam tikros klasės ir likusios sistemos tarpusavio priklausomybės.

Veikiantis pavyzdys

Tačiau praktiškesniu lygiu galite naudoti „Reflection“ API, kad išmestumėte klasę, kaip ir aš sąvartynas klasė padarė praėjusio mėnesio skiltyje.

Norėdami pademonstruoti „Reflection API“, parašiau klasę pavadinimu „ReflectClass“ tam prireiks „Java“ vykdymo laiko žinomos klasės (tai reiškia, kad ji kažkur yra jūsų klasės kelyje) ir per „Reflection“ API išmes savo struktūrą į terminalo langą. Norėdami eksperimentuoti su šia klase, turite turėti JDK 1.1 versiją.

Pastaba: atlikite ne pabandykite naudoti 1.0 vykdymo laiką, nes jis supainiojamas, todėl paprastai atsiranda nesuderinama klasės keitimo išimtis.

Klasė „ReflectClass“ prasideda taip:

importuoti java.lang.reflect. *; importuoti java.util. *; viešoji klasė „ReflectClass“ 

Kaip matote aukščiau, pirmas dalykas, kurį daro kodas, yra „Reflection API“ klasių importavimas. Tada jis pereina tiesiai į pagrindinį metodą, kuris prasideda taip, kaip parodyta žemiau.

 public static void main (String args []) {Konstruktorius cn []; Cc klasė []; Metodas mm []; Laukas ff []; C klasė = nulis; Klasės supClass; Stygos x, y, s1, s2, s3; „Hashtable classRef“ = nauja „Hashtable“ (); if (args.length == 0) {System.out.println ("Komandinėje eilutėje nurodykite klasės pavadinimą."); „System.exit“ (1); } pabandykite {c = Class.forName (argumentai [0]); } catch (ClassNotFoundException ee) {System.out.println ("Nepavyko rasti klasės '" + args [0] + "'"); „System.exit“ (1); } 

Metodas pagrindinis deklaruoja konstruktorių, laukų ir metodų masyvus. Jei prisiminsite, tai yra trys iš keturių pagrindinių klasės bylos dalių. Ketvirtoji dalis yra atributai, prie kurių „Reflection“ API, deja, nesuteikia prieigos. Po masyvų atlikau komandų eilutės apdorojimą. Jei vartotojas įvedė klasės pavadinimą, kodas bando jį įkelti naudodamas forName klasės metodas Klasė. forName metodas reikalauja „Java“ klasių pavadinimų, o ne failų pavadinimų, taigi, norint pažvelgti į „ java.math.BigInteger klasėje, tiesiog įveskite „java ReflectClass java.math.BigInteger“, o ne nurodykite, kur klasės failas iš tikrųjų yra saugomas.

Klasės paketo identifikavimas

Darant prielaidą, kad randamas klasės failas, kodas pereina prie 0 veiksmo, kuris parodytas žemiau.

 / * * 0 žingsnis: Jei mūsų varde yra taškų, mes esame pakuotėje, todėl pirmiausia įdėkite * tai. * / x = c.getName (); y = x.substringas (0, x.lastIndexOf (".")); if (y.length ()> 0) {System.out.println ("paketas" + y + "; \ n \ r"); } 

Šiame žingsnyje klasės pavadinimas gaunamas naudojant getName metodas klasėje Klasė. Šis metodas pateikia visiškai kvalifikuotą pavadinimą ir, jei pavadinime yra taškų, galime manyti, kad klasė buvo apibrėžta kaip paketo dalis. Taigi 0 žingsnis yra atskirti paketo pavadinimo dalį nuo klasės pavadinimo dalies ir atsispausdinti paketo pavadinimo dalį eilutėje, prasidedančioje „paketas ....“.

Klasių nuorodų rinkimas iš deklaracijų ir parametrų

Pasirūpinę paketo išrašu, pereiname prie 1 veiksmo, ty surinkti visus kita klasės pavadinimai, į kuriuos nurodo ši klasė. Šis rinkimo procesas parodytas žemiau esančiame kode. Atminkite, kad trys dažniausiai pasitaikančios vietos, kur nurodomi klasių pavadinimai, yra laukų tipai (egzempliorių kintamieji), metodų grąžinimo tipai ir metodams bei konstruktoriams perduotų parametrų tipai.

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

Pirmiau pateiktame kode masyvas ff yra inicializuotas kaip Laukas objektai. Kilpa renka tipo pavadinimą iš kiekvieno lauko ir apdoroja jį per tPavadinimas metodas. tPavadinimas metodas yra paprastas pagalbininkas, kuris grąžina tipo sutrumpinimą. Taigi java.lang.Stringas tampa Stygos. Ir jis hashtable'e pažymi, kurie objektai buvo matyti. Šiame etape kodas yra labiau suinteresuotas rinkti klasių nuorodas nei spausdinti.

Kitas klasių nuorodų šaltinis yra konstruktoriams pateikti parametrai. Kitas kodas, parodytas žemiau, apdoroja kiekvieną deklaruotą konstruktorių ir renka nuorodas iš parametrų sąrašų.

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

Kaip matote, aš naudojau getParameterTypes metodas Konstruktorius klasę, kad paduotų man visus parametrus, kurių imasi konkretus konstruktorius. Tada jie apdorojami per tPavadinimas metodas.

Įdomus dalykas, kurį reikia atkreipti dėmesį į tai, yra metodo skirtumas „getDeclaredConstructors“ ir metodas „getConstructors“. Abu metodai pateikia konstruktorių masyvą, tačiau „getConstructors“ metodas pateikia tik tuos konstruktorius, kurie yra prieinami jūsų klasei. Tai naudinga, jei norite sužinoti, ar iš tikrųjų galite iškviesti rastą konstruktorių, tačiau tai nėra naudinga šiai programai, nes noriu atsispausdinti visus klasės konstruktorius, viešuosius ar ne. Lauko ir metodo atšvaitai taip pat turi panašias versijas, viena skirta visiems nariams ir viena skirta tik viešiesiems nariams.

Paskutinis žingsnis, parodytas žemiau, yra surinkti visų metodų nuorodas. Šis kodas turi gauti nuorodas tiek iš metodo tipo (panašaus į aukščiau esančius laukus), tiek iš parametrų (panašių į aukščiau pateiktus konstruktorius).

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

Pirmiau pateiktame kode yra du skambučiai tPavadinimas - vienas rinkti grąžinimo tipą ir vienas kiekvieno parametro tipui rinkti.