Programmēšana

Padziļināti apskatiet Java Reflection API

Pagājušā mēneša "Java In-Depth" rakstā es runāju par introspekciju un veidiem, kā Java klase ar piekļuvi neapstrādātiem klases datiem varētu izskatīties klases "iekšienē" un saprast, kā klase tika uzbūvēta. Turklāt es parādīju, ka, pievienojot klases iekrāvēju, šīs klases varēja ielādēt skriešanas vidē un izpildīt. Šis piemērs ir statisks pašpārbaude. Šomēnes apskatīšu Java Reflection API, kas dod Java klasēm iespēju uzstāties dinamisks introspekcija: spēja ielūkoties jau ielādētu klasēs.

Introspekcijas lietderība

Viena no Java priekšrocībām ir tā, ka tā tika izstrādāta, pieņemot, ka vide, kurā tā darbojās, mainīsies dinamiski. Klases tiek ielādētas dinamiski, saistīšana tiek veikta dinamiski, un objektu gadījumi tiek dinamiski izveidoti lidojuma laikā, kad tie ir nepieciešami. Tas, kas vēsturiski nav bijis ļoti dinamisks, ir spēja manipulēt ar “anonīmām” klasēm. Šajā kontekstā anonīma klase ir tā klase, kas izpildes laikā tiek ielādēta vai parādīta Java klasei un kuras tips Java programmai iepriekš nebija zināms.

Anonīmās nodarbības

Anonīmu nodarbību atbalstīšanu ir grūti izskaidrot, un programmā to ir vēl grūtāk noformēt. Anonīmās klases atbalstīšanas izaicinājumu var izteikt šādi: "Uzrakstiet programmu, kas, piešķirot Java objektu, var iekļaut šo objektu tās turpmākajā darbībā." Vispārējais risinājums ir diezgan grūts, taču, ierobežojot problēmu, var izveidot dažus specializētus risinājumus. Šīs Java klases 1.0 versijā ir divi specializētu risinājumu piemēri: Java sīklietotnes un Java tulka komandrindas versija.

Java sīklietotnes ir Java klases, kuras tīmekļa pārlūkprogrammas kontekstā ielādē darbojas Java virtuālā mašīna un kuras izsauc. Šīs Java klases ir anonīmas, jo izpildes laiks pirms laika nezina nepieciešamo informāciju katras klases izsaukšanai. Tomēr konkrētas klases izsaukšanas problēma tiek atrisināta, izmantojot Java klasi java.applet.Applet.

Kopīgas superklases, piemēram Apletsun Java saskarnes AppletContext, risiniet anonīmo klašu problēmu, izveidojot iepriekš saskaņotu līgumu. Konkrēti, izpildlaika vides piegādātājs reklamē, ka var izmantot jebkuru objektu, kas atbilst norādītajam interfeisam, un izpildlaika vides patērētājs izmanto šo norādīto interfeisu jebkurā objektā, kuru viņš plāno piegādāt izpildes laikam. Sīklietotņu gadījumā ir precīzi norādīts interfeiss kopējas superklases formā.

Kopējā superklases risinājuma mīnuss, it īpaši, ja nav vairāku mantojumu, ir tas, ka objektus, kas izveidoti darbam vidē, nevar izmantot arī kādā citā sistēmā, ja vien šī sistēma neīsteno visu līgumu. Gadījumā, ja no Aplets saskarnes, ir jāīsteno mitināšanas vide AppletContext. Ko tas nozīmē sīklietotņu risinājumam, tas ir tas, ka risinājums darbojas tikai tad, kad ielādējat sīklietotnes. Ja jūs ievietojat a Hashtable objektu savā tīmekļa lapā un norādiet uz to pārlūkprogrammai, to neizdodas ielādēt, jo sīklietotņu sistēma nevar darboties ārpus tā ierobežotā diapazona.

Papildus sīklietotņu piemēram introspekcija palīdz atrisināt problēmu, kuru pieminēju pagājušajā mēnesī: izdomāt, kā sākt izpildi klasē, kuru tikko ielādēja Java virtuālās mašīnas komandrindas versija. Šajā piemērā virtuālajai mašīnai ielādētajā klasē ir jāizsauc kāda statiska metode. Pēc vienošanās šī metode tiek nosaukta galvenais un ņem vienu argumentu - masīvu Stīga objektiem.

Motivācija dinamiskākam risinājumam

Esošās Java 1.0 arhitektūras izaicinājums ir tāds, ka pastāv problēmas, kuras varētu atrisināt ar dinamiskāku savvaļas vidi - piemēram, ielādējami lietotāja interfeisa komponenti, ielādējami ierīču draiveri Java balstītā OS un dinamiski konfigurējamas rediģēšanas vides. "Lietotne killer" jeb problēma, kuras dēļ tika izveidota Java Reflection API, bija Java objekta komponenta modeļa izstrāde. Šis modelis tagad ir pazīstams kā JavaBeans.

Lietotāja saskarnes komponenti ir ideāls introspekcijas sistēmas projektēšanas punkts, jo tiem ir divi ļoti atšķirīgi patērētāji. No vienas puses, komponentu objekti tiek sasaistīti kopā, veidojot lietotāja saskarni kā daļu no lietojumprogrammas. Alternatīvi, ir jābūt saskarnei rīkiem, kas manipulē ar lietotāja komponentiem, nezinot, kas ir komponenti, vai, vēl svarīgāk, bez piekļuves komponentu avota kodam.

Java Reflection API izauga no JavaBeans lietotāja saskarnes komponenta API vajadzībām.

Kas ir pārdomas?

Būtībā Reflection API sastāv no diviem komponentiem: objektiem, kas attēlo dažādas klases faila daļas, un līdzekli šo objektu izdalīšanai drošā un drošā veidā. Pēdējais ir ļoti svarīgs, jo Java nodrošina daudz drošības garantiju, un nebūtu jēgas nodrošināt klašu kopumu, kas šos aizsardzības pasākumus padarīja nederīgus.

Pirmais Reflection API komponents ir mehānisms, ko izmanto, lai iegūtu informāciju par klasi. Šis mehānisms ir iebūvēts nosauktajā klasē Klase. Īpašā klase Klase ir universāls veids meta informācijai, kas apraksta objektus Java sistēmā. Klases iekrāvēji Java sistēmā atgriež veida objektus Klase. Līdz šim trīs interesantākās metodes šajā klasē bija:

  • forName, kas ielādētu kāda nosaukuma klasi, izmantojot pašreizējo klases iekrāvēju

  • getName, kas atgriezīs klases nosaukumu kā a Stīga objekts, kas bija noderīgs objektu atsauču identificēšanai pēc to klases nosaukuma

  • newInstance, kas klasē izsauktu nulles konstruktoru (ja tāds pastāv) un atgrieztu šīs objekta klases objekta instanci

Šīm trim noderīgajām metodēm Reflection API klasē pievieno dažas papildu metodes Klase. Tie ir šādi:

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

Papildus šīm metodēm tika pievienotas daudzas jaunas klases, lai attēlotu objektus, kurus šīs metodes atgriezīs. Jaunās klases lielākoties ir java.lang.reflect pakete, bet dažas no jaunajām pamata tipa klasēm (Void, Baitsun tā tālāk) ir java.lang iepakojums. Tika pieņemts lēmums jaunās klases ievietot tur, kur tās atrodas, pārdomu paketē ievietojot klases, kas atspoguļoja metadatus, un valodas paketē klases, kas pārstāvēja tipus.

Tādējādi Reflection API atspoguļo vairākas klases izmaiņas Klase kas ļauj jums uzdot jautājumus par klases iekšējiem priekšmetiem un virkni nodarbību, kas atspoguļo atbildes, ko jums sniedz šīs jaunās metodes.

Kā es varu izmantot Reflection API?

Jautājums "Kā es varu izmantot API?" varbūt ir interesantāks jautājums nekā "Kas ir pārdomas?"

Reflection API ir simetrisks, kas nozīmē, ka, ja jūs turat a Klase objektu, jūs varat jautāt par tā iekšējām daļām, un, ja jums ir kāds no iekšējiem, varat pajautāt, kura klase to deklarēja. Tādējādi jūs varat pārvietoties uz priekšu un atpakaļ no klases uz metodi uz parametru uz klases utt. Viens interesants šīs tehnoloģijas pielietojums ir noskaidrot lielāko daļu savstarpējo atkarību starp konkrēto klasi un pārējo sistēmu.

Darbīgs piemērs

Tomēr praktiskākā līmenī jūs varat izmantot Reflection API, lai izmestu klasi, tāpat kā es izgāztuve klase darīja pagājušā mēneša slejā.

Lai parādītu Reflection API, es uzrakstīju klasi ar nosaukumu ReflectClass tas aizņemtu Java izpildes laikam zināmu klasi (tas nozīmē, ka tas kaut kur atrodas jūsu klases ceļā) un, izmantojot Reflection API, izkārtotu tās struktūru termināla logā. Lai eksperimentētu ar šo klasi, jums būs jābūt pieejamai JDK 1.1 versijai.

Piezīme: Darietmēģiniet izmantot 1.0 darbības laiku, jo tas viss sajaucas, kā rezultātā parasti rodas nesaderīgs klases maiņas izņēmums.

Klase ReflectClass sākas šādi:

importēt java.lang.reflect. *; importēt java.util. *; publiskā klase ReflectClass { 

Kā redzat iepriekš, pirmais, ko kods dara, ir Reflection API klases importēšana. Tālāk tas pāriet tieši uz galveno metodi, kas sākas, kā parādīts zemāk.

 public static void main (String args []) {Constructor cn []; Cc klase []; Metode mm []; Lauks ff []; C klase = nulle; Klases supClass; Virkne x, y, s1, s2, s3; Hashtable classRef = jauns Hashtable (); if (args.length == 0) {System.out.println ("Lūdzu, komandrindā norādiet klases nosaukumu."); System.exit (1); } mēģiniet {c = Class.forName (argumenti [0]); } catch (ClassNotFoundException ee) {System.out.println ("Nevarēja atrast klasi" "+ args [0] +" ""); System.exit (1); } 

Metode galvenais deklarē konstruktoru, lauku un metožu masīvus. Ja atceraties, šīs ir trīs no četrām klases faila pamatdaļām. Ceturtā daļa ir atribūti, kuriem Reflection API diemžēl nedod piekļuvi. Pēc masīviem esmu paveicis komandrindas apstrādi. Ja lietotājs ir ierakstījis klases nosaukumu, kods mēģina to ielādēt, izmantojot forName klases metode Klase. The forName metode ņem Java klašu nosaukumus, nevis failu nosaukumus, tāpēc ieskatieties java.math.BigInteger klasē, jūs vienkārši ierakstāt "java ReflectClass java.math.BigInteger", nevis norādāt, kur klases fails faktiski tiek glabāts.

Klases paketes identificēšana

Pieņemot, ka klases fails ir atrasts, kods pāriet uz 0. darbību, kas parādīta zemāk.

 / * * 0. solis: Ja mūsu nosaukumā ir punkti, mēs esam iepakojumā, tāpēc vispirms izlieciet to *. * / x = c.getName (); y = x.substring (0, x.lastIndexOf (".")); if (y.length ()> 0) {System.out.println ("pakete" + y + "; \ n \ r"); } 

Šajā solī klases nosaukums tiek izgūts, izmantojot getName metode klasē Klase. Šī metode atgriež pilnīgi kvalificēto nosaukumu, un, ja nosaukumā ir punkti, mēs varam pieņemt, ka klase tika definēta kā daļa no paketes. Tātad 0. solis ir atdalīt pakotnes nosaukuma daļu no klases nosaukuma daļas un izdrukāt pakotnes nosaukuma daļu rindā, kas sākas ar "pakete ...."

Klases atsauču apkopošana no deklarācijām un parametriem

Rūpējoties par pakotnes izrakstu, mēs pārietam uz 1. darbību, proti, apkopot visu cits klašu nosaukumi, uz kuriem atsaucas šī klase. Šis savākšanas process ir parādīts zemāk esošajā kodā. Atcerieties, ka trīs visizplatītākās vietas, uz kurām attiecas klases nosaukumi, ir lauku tipi (instances mainīgie), metožu atgriešanas tipi un metodēm un konstruktoriem nodoto parametru veidi.

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

Iepriekš minētajā kodā masīvs ff tiek inicializēts kā Lauks objektiem. Cilpa savāc tipa nosaukumu no katra lauka un apstrādā to caur tName metodi. The tName metode ir vienkāršs palīgs, kas atgriež tipa stenogrāfijas nosaukumu. Tātad java.lang.Strings kļūst Stīga. Un tā hashtable atzīmē, kuri objekti ir redzēti. Šajā posmā kodu vairāk interesē klases atsauču apkopošana nekā drukāšana.

Nākamais klases atsauces avots ir konstruktoriem piegādātie parametri. Nākamais koda gabals, kas parādīts zemāk, apstrādā katru deklarēto konstruktoru un apkopo atsauces no parametru sarakstiem.

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

Kā redzat, es esmu izmantojis getParameterTypes metodi Konstruktors klasē, lai man padotu visus parametrus, ko ņem konkrētais konstruktors. Pēc tam tos apstrādā caur tName metodi.

Interesanta lieta, kas šeit jāatzīmē, ir atšķirība starp metodi getDeclaredConstructors un metodi getConstructors. Abas metodes atgriež konstruktoru masīvu, bet getConstructors method atgriež tikai tos konstruktorus, kuri ir pieejami jūsu klasei. Tas ir noderīgi, ja vēlaties uzzināt, vai tiešām varat izsaukt atrasto konstruktoru, taču tas nav noderīgi šai lietojumprogrammai, jo es vēlos izdrukāt visus klases konstruktorus, publiskus vai nē. Arī lauka un metodes atstarotājiem ir līdzīgas versijas - viena paredzēta visiem dalībniekiem un otra - tikai sabiedrības locekļiem.

Pēdējais solis, kas parādīts zemāk, ir apkopot atsauces no visām metodēm. Šim kodam ir jāsaņem atsauces gan no metodes veida (līdzīgi kā iepriekš minētie lauki), gan no parametriem (līdzīgi kā iepriekšējie konstruktori).

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

Iepriekš minētajā kodā ir divi zvani uz tName - viens, lai savāktu atgriešanās tipu, un viens, lai savāktu katra parametra tipu.

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