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útomTYPE
: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
- Wikipedia: Reflection (computer programming) (13. 4. 2016)
- Wikipedia: Reflexe (programování) (13. 4. 2016)
- Oracle: Trail: The Reflection API (14. 4. 2016)
- Oracle: Package java.lang.reflect (13. 4. 2016)