Java – reflexia

Reflexia je schopnosť programu za behu skúmať a modifikovať svoju vlastnú štruktúru a správanie. K tomuto účelu nám musí programovací jazyk poskytovať určité nástroje, v Jave sú obsiahnuté v balíčku java.lang.reflect a triede java.lang.Class.

java.lang.Class

Každý objekt v Jave je buď odkaz (objekt odvodený od java.lang.Object) alebo primitívny typ (boolean, byte, char, short, int, long, float, double). Medzi objekty typu odkaz patria triedy, enumerácie, polia a rozhrania.

Pre každý objekt v Jave (t.j. aj pre primitívny typ) sa v JVM vytvára inštancia triedy Class, tá predstavuje vstupný bod k reflexii, pretože triedy v balíčku reflect nemajú verejné konštruktory a ich inštancie získavame práve cez metódy triedy Class.

Trieda Class umožňuje vytvárať nové inštancie triedy alebo získavať informácie z už existujúcich:

  • názov a druh typu (trieda, enumerácia, pole, rozhranie),
  • anotácie,
  • členov typu (atribúty, konštruktory, metódy, vnorené dátové typy).

Získanie inštancie triedy Class

Samotnú triedu Class môžeme získať:

  • z typu objektu atribútom class:
    Class c1 = boolean.class; // vráti Class pre primitívny typ boolean
    Class c2 = Color.class;   // vráti Class pre triedu Color
  • z existujúceho objektu metódou getClass():
    Class red = new Color();
    Class c1 = red.getClass();       // vráti Class pre triedu Color
    Class c2 = "example".getClass(); // vráti Class pre triedu String
  • z plného mena triedy pomocou statickej metódy Class.forName():
    Class c = Class.forName("sk.example.MyClass"); // vráti Class pre triedu MyClass
  • z obaľovačov primitívnych typov a void atribútom TYPE:
    Class c1 = Double.TYPE; // vráti Class identickú s double.class
    Class c2 = Void.TYPE;   // vráti Class identickú s void.class
  • špeciálnymi metódami z už získanej inštancie Class:
    Class c = ...;
    
    // vráti Class nadradenej triedy
    Class c1 = c.getSuperClass();
    
    // vráti Class všetkých verejných tried, rozhraní a enumerácii, ktoré sú členmi danej triedy (aj zdedené)
    Class c2 = c.getClasses();
    
    // vráti Class všetkých tried, rozhraní a enumerácii explicitne deklarovaných v danej triede (aj privátne)
    Class c3 = c.getDeclaredClasses();
    
    // vráti Class triedy, v ktorej je daná trieda deklarovaná
    Class c4 = c.getDeclaringClass();
    
    // vráti Class triedy, v ktorej je daná anonymná trieda
    Class c5 = c.getEnclosingClass();

Získavanie informácii o triedach, modifikátoroch a typoch

Keď už máme inštanciu triedy Class, môžeme pomocou jej metód získať napr. nasledujúce údaje:

Class<?> c = Class.forName("X");

// názov triedy
String name = c.getCanonicalName();

// názov balíčku, v ktorom je daná trieda
Package package = c.getPackage();
package.getName();

// modifikátory, hodnotu treba dekódovať triedou java.lang.reflect.Modifier
int modifiers = c.getModifiers();
Strig decoded = Modifier.toString(modifiers);
boolean public = Modifier.isPublic(modifiers);
…

// pole s generickými typmi triedy
TypeVariable genericTypes = c.getTypeParameters();
genericTypes[i].getName();

// pole s typmi rozhraní, ktoré daná trieda priamo implementuje alebo s typom rozhranie, ktoré daný objekt reprezentuje
Type[] interfacesTypes = c.getGenericInterfaces();
interfacesTypes[i].toString();

Annotation[] annotations = c.getAnnotations();
annotations[i].toString();

Získavanie informácii o členoch triedy

Trieda Class poskytuje aj metódy na pristupovanie k atribútom, metódam a konštruktorom. Môžeme ich rozdeliť podľa dvoch kritérií na tie, ktoré:

  • vracajú zoznam členov tried vs. hľadajú konkrétnych členov tried,
  • pristupujú k členom deklarovaným priamo v triede vs. členom zdedených z nadradených tried alebo rozhraní.

Prehľad týchto triede môžeme vidieť v nasledujúcich tabuľkách:

  • metódy na prácu s atribútmi:
    Metóda
    Zoznam členov
    Zdedení členovia
    Privátni členovia
    getDeclaredField()
    nie
    nie
    áno
    getField()
    nie
    áno
    nie
    getDeclaredFields()
    áno
    nie
    áno
    getFields()
    áno
    áno
    nie
  • metódy na prácu s metódami: 
    Metóda
    Zoznam členov
    Zdedení členovia
    Privátni členovia
    getDeclaredMethod()
    nie
    nie
    áno
    getMethod()
    nie
    áno
    nie
    getDeclaredMethods()
    áno
    nie
    áno
    getMethods()
    áno
    áno
    nie
  • metódy na prácu s konštruktormi: 
    Metóda
    Zoznam členov
    Zdedení členovia
    Privátni členovia
    getDeclaredConstructor()
    nie
    konštruktory sa nededia
    áno
    getConstructor()
    nie
    konštruktory sa nededia
    nie
    getDeclaredConstructors()
    áno
    konštruktory sa nededia
    áno
    getConstructors()
    áno
    konštruktory sa nededia
    nie

Tieto metódy vracajú objekty rozhrania java.lang.reflect.Member (implementované triedami Field, Method, Constructor) alebo polia týchto objektov, ich metódy nám umožňujú s nimi ďalej pracovať.

Vytváranie inštancií tried

Nové inštancie tried môžeme vytvárať pomocou reflexie dvomi rôznymi metódami – java.lang.reflect.Constructor.newInstance() alebo Class.newInstance(). Ta prvá je preferovaná, pretože prináša viacero výhod, ktoré sú uvedené v nasledujúcej tabuľke:

Constructor.newInstance()Class.newInstance()
Dokáže vyvolať ľubovoľný konštruktor danej triedy.Dokáže vyvolať len bezparametrický konštruktor.
Vyhodí akúkoľvek (stráženú aj nestráženú) výnimku vyhodenú konštruktorom.Zabalí vyhodenú výnimku do výnimky InvocationTargetException.
Vyžaduje, aby konštruktor bol viditeľný.Dokáže vyvolať za určitých okolností aj privátne konštruktory.

Príklad použitia pozri na docs.oracle.com.

Využitie reflexie v praxi

Reflexia umožňuje vykonávať operácie, ktoré by inak neboli možné, v praxi sa využíva:

  • na tvorbu testovacieho softvéru,
  • na dosiahnutie dynamickej adaptácie programu na rôzne situácie,
  • na zmenu viditeľnosti čelno tried,

Vo všeobecnosti sa však odporúča, ak je možné operáciu vykonať bez reflexie, mali by sme sa jej vyhnúť.

Zdroje

Leave a Reply

Vaša e-mailová adresa nebude zverejnená. Vyžadované polia sú označené *