Reflexe v Javě
Ještě než opustíme OOP sekci, chtěl bych vás seznámit s pokročilou technikou - Reflexí.
Reflexe
Reflexe je nástroj, pomocí kterého lze zjistit za běhu programu informace o nějaké třídě nebo instanci, přistoupit k jejím atributům a v neposlední řadě měnit hodnoty těchto atributů.
K čemu je to dobré?
Díky reflexi můžeme např. vytvořit instanci třídy, jejíž název nám
někde přišel jako String a před spuštěním programu tedy
nevíme, jakou instanci zde budeme vytvářet. To se může hodit např. při
parsování nějakých textových souborů, o kterých předem nevíme, co
obsahují. Možná jste již někdy použili bezduchá větvení typu:
if (atribut == "jmeno") { uzivatel.jmeno = hodnota; } else if (atribut == "prijmeni") { uzivatel.prijmeni = hodnota; }
Reflexe nám v této situaci umožní sáhnout na atribut podle jeho názvu
předaném jako String a objektu, kterému má patřit.
Reflexe je ovšem ale ještě mocnější nástroj, který může ve
špatných rukou nadělat velkou paseku. Na konci dnešní lekce si ukážeme,
jak pomocí ní lze modifikovat private atributy zvenčí. Ouch
Reflexe je také relativně
náročná na paměť, proto byste tuto techniku měli využívat jen
minimálně a v krajních případech, kde dává smysl. Můžeme ji použít
např. při již zmíněném parsování, nikdo nemá rád kilometrové
konstrukce switch 
class
Než se pustíme do praxe, je třeba trocha teorie. Seznámíme se nejdříve
s metodami, pomocí kterých můžeme s třídami manipulovat. K metodám pro
reflexi se dostaneme přes třídu, kterou chceme zkoumat/upravovat pomocí
magického slova class, tedy:
NejakaTrida.class
Každé chytré IDE nám dokáže napovědět, které metody jsou k dispozici. Zde je výčet těch "nejdůležitějších".
Základní informace
Pro získání základních informací o třídě použijeme metody:
getPackage()- Vrátí package, ve kterém se třída nachází.getName()- Vrátí název třídy.getConstructors()- Vrátí pole všech konstruktorů.getConstructor(String name)- Vrátí instanci konkrétního konstruktoru.getSuperclass()- Vrátí typ předka.getAnnotations()- Vrátí pole anotací třídy.getModifiers()- Vrátí číslo, které obsahuje zakódovaný seznam modifikátorů.
Atributy a metody
Pro práci s atributy a metodami dané třídy použijeme:
getFields()- Vrátí pole atributů třídy včetně zděděných.getField(String name)- Vrátí instanci jednoho atributu třídy.getMethods()- Vrátí pole metod v testované třídě včetně zděděných.getMethod(String name)- Vrátí instanci jedné metody v testované třídě.
getDeclaredFields()- Vrátí pole deklarovaných atributů třídy.getDeclaredField(String name)- Vrátí instanci jednoho deklarovaného atributu třídy.getDeclaredMethods()- Vrátí pole deklarovaných metod v testované třídě.getDeclaredMethod(String name)- Vrátí instanci jedné metody v testované třídě.
isInterface()- Vrátí hodnotutrue, pokud se jedná o rozhraní, jinak hodnotufalse.isEnum()- Vrátí jodnotutrue, pokud se jedná oenum, jinak hodnotufalse.
Pokud použijeme gettery na declared pro
získání konkrétní metody/atributu a odkážeme se přitom na metodu/atribut
předka, vyvolá se výjimka NoSuchMethodException
Metody instancí
Některé gettery nám vrací instanci tříd, například metody:
getConstructor(),getMethod(),getField()agetAnnotations().
Nad instancemi těchto tříd lze volat velké množství stejných metod, které jsou uvedené výše. Pro konstruktory a metody přibyly metody pro získání jejich parametrů, případně návratového typu:
getParameterTypes()- Vrátí pole typů parametrů.getReturnType()- Vrátí návratový datový typ.
Modifikátory
Dále jsme zmínili metodu getModifiers(), která vrátí
záhadné číslo. Toto číslo nám pomůže dekódovat třída
Modifier, která nám nabízí statické metody pro zjištění
jednotlivých modifikátorů:
Modifier.isPublic(modifier)Modifier.isPrivate(modifier)Modifier.isProtected(modifier)Modifier.isStatic(modifier)Modifier.isFinal(modifier)Modifier.isSynchronized(modifier)Modifier.isVolatile(modifier)Modifier.isTransient(modifier)Modifier.isNative(modifier)Modifier.isAbstract(modifier)
Kostka
Dost bylo teorie, jdeme si výše zmíněné metody vyzkoušet v praxi.
Vzpomínáte si na jednoduchou hrací kostku z jedné
lekce kurzu? Dnes vše budeme demonstrovat právě na třídě
Kostka. Její kód je následující:
public class Kostka { private Random random; private int pocetSten; public Kostka() { this.pocetSten = 6; random = new Random(); } public Kostka(int pocetSten) { this.pocetSten = pocetSten; random = new Random(); } public int vratPocetSten() { return pocetSten; } public int hod() { return random.nextInt(pocetSten + 1) + 1; } @Override public String toString() { return String.format("Kostka s %s stěnami", pocetSten); } }
Získání informací o třídě za běhu programu
Pomocí reflexe si necháme vypsat podrobné informace o této třídě:
System.out.println("Název třídy: " + Kostka.class.getName()); System.out.println("----- Konstruktory třídy ------"); final Constructor<?>[] constructors = Kostka.class.getConstructors(); for (Constructor<?> constructor : constructors) { System.out.println("\t Název: " + constructor.getName()); System.out.println("\t\t Parametry konstruktoru: " + Arrays.toString(constructor.getParameterTypes())); } System.out.println(); System.out.println("------- Atributy třídy --------"); final Field[] fields = Kostka.class.getDeclaredFields(); for (Field field : fields) { System.out.println("\t Název: " + field.getName()); final int modifier = field.getModifiers(); System.out.println("\t\t Modifiers.isPublic: " + Modifier.isPublic(modifier)); System.out.println("\t\t Modifiers.isPrivate: " + Modifier.isPrivate(modifier)); System.out.println("\t\t Modifiers.isProtected: " + Modifier.isProtected(modifier)); System.out.println("\t\t Modifiers.isStatic: " + Modifier.isStatic(modifier)); System.out.println("\t\t Modifiers.isFinal: " + Modifier.isFinal(modifier)); System.out.println("\t\t Modifiers.isSynchronized: " + Modifier.isSynchronized(modifier)); System.out.println("\t\t Modifiers.isVolatile: " + Modifier.isVolatile(modifier)); System.out.println("\t\t Modifiers.isTransient: " + Modifier.isTransient(modifier)); System.out.println("\t\t Modifiers.isNative: " + Modifier.isNative(modifier)); System.out.println("\t\t Modifiers.isAbstract: " + Modifier.isAbstract(modifier)); } final Method[] methods = Kostka.class.getDeclaredMethods(); System.out.println("Declared methods: " + Arrays.asList(methods)); System.out.println("Methods: " + Arrays.asList(Kostka.class.getMethods())); for (Method method : methods) { System.out.println("\t Název: " + method.getName()); final int modifier = method.getModifiers(); System.out.println("\t\t Modifiers.isPublic: " + Modifier.isPublic(modifier)); System.out.println("\t\t Modifiers.isPrivate: " + Modifier.isPrivate(modifier)); System.out.println("\t\t Modifiers.isProtected: " + Modifier.isProtected(modifier)); System.out.println("\t\t Modifiers.isStatic: " + Modifier.isStatic(modifier)); System.out.println("\t\t Modifiers.isFinal: " + Modifier.isFinal(modifier)); System.out.println("\t\t Modifiers.isSynchronized: " + Modifier.isSynchronized(modifier)); System.out.println("\t\t Modifiers.isVolatile: " + Modifier.isVolatile(modifier)); System.out.println("\t\t Modifiers.isTransient: " + Modifier.isTransient(modifier)); System.out.println("\t\t Modifiers.isNative: " + Modifier.isNative(modifier)); System.out.println("\t\t Modifiers.isAbstract: " + Modifier.isAbstract(modifier)); System.out.println("\t\t Parametry: " + Arrays.toString(method.getParameterTypes())); System.out.println("\t\t Návratový typ: " + method.getReturnType()); }
Nezapomeneme na importy:
import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays;
Výsledek výpisu bude vypadat takto:
Konzolová aplikace
Název třídy: cz.itnetwork.reflexe.Kostka
----- Konstruktory třídy ------
Název: cz.itnetwork.reflexe.Kostka
Parametry konstruktoru: []
Název: cz.itnetwork.reflexe.Kostka
Parametry konstruktoru: [int]
------- Atributy třídy --------
Název: random
Modifiers.isPublic: false
Modifiers.isPrivate: true
Modifiers.isProtected: false
Modifiers.isStatic: false
Modifiers.isFinal: false
Modifiers.isSynchronized: false
Modifiers.isVolatile: false
Modifiers.isTransient: false
Modifiers.isNative: false
Modifiers.isAbstract: false
Název: pocetSten
Modifiers.isPublic: false
Modifiers.isPrivate: true
Modifiers.isProtected: false
Modifiers.isStatic: false
Modifiers.isFinal: false
Modifiers.isSynchronized: false
Modifiers.isVolatile: false
Modifiers.isTransient: false
Modifiers.isNative: false
Modifiers.isAbstract: false
Declared methods: [
public java.lang.String cz.itnetwork.reflexe.Kostka.toString(),
public int cz.itnetwork.reflexe.Kostka.vratPocetSten(),
public int cz.itnetwork.reflexe.Kostka.hod()
]
Methods: [
public java.lang.String cz.itnetwork.reflexe.Kostka.toString(),
public int cz.itnetwork.reflexe.Kostka.vratPocetSten(),
public int cz.itnetwork.reflexe.Kostka.hod(),
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException,
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException,
public final void java.lang.Object.wait() throws java.lang.InterruptedException,
public boolean java.lang.Object.equals(java.lang.Object),
public native int java.lang.Object.hashCode(),
public final native java.lang.Class java.lang.Object.getClass(),
public final native void java.lang.Object.notify(),
public final native void java.lang.Object.notifyAll()
]
Název: toString
Modifiers.isPublic: true
Modifiers.isPrivate: false
Modifiers.isProtected: false
Modifiers.isStatic: false
Modifiers.isFinal: false
Modifiers.isSynchronized: false
Modifiers.isVolatile: false
Modifiers.isTransient: false
Modifiers.isNative: false
Modifiers.isAbstract: false
Parametry: []
Návratový typ: class java.lang.String
Název: vratPocetSten
Modifiers.isPublic: true
Modifiers.isPrivate: false
Modifiers.isProtected: false
Modifiers.isStatic: false
Modifiers.isFinal: false
Modifiers.isSynchronized: false
Modifiers.isVolatile: false
Modifiers.isTransient: false
Modifiers.isNative: false
Modifiers.isAbstract: false
Parametry: []
Návratový typ: int
Název: hod
Modifiers.isPublic: true
Modifiers.isPrivate: false
Modifiers.isProtected: false
Modifiers.isStatic: false
Modifiers.isFinal: false
Modifiers.isSynchronized: false
Modifiers.isVolatile: false
Modifiers.isTransient: false
Modifiers.isNative: false
Modifiers.isAbstract: false
Parametry: []
Návratový typ: int
Modifikace atributů
Nakonec si ukážeme, proč je reflexe tak mocný nástroj. Třída
Kostka si uchovává informaci o počtu stěn v
privátním atributu pocetSten. Prvně si
vytvoříme instanci kostky. Z té si pomocí reflexe vytáhneme její atribut
pocetSten, resp. získáme instanci, která tento atribut
reprezentuje. Té potom upravíme její hodnotu na novou. Voilà, právě jsme
přepsali hodnotu privátního atributu zvenčí!
Připravíme si následující kód:
Kostka kostka = new Kostka(); System.out.println("Originální počet stěn: " + kostka.vratPocetSten()); final java.lang.reflect.Field pocetStenField = Kostka.class.getDeclaredField("pocetSten"); pocetStenField.set(kostka, 12); System.out.println("Nový počet stěn: " + kostka.vratPocetSten());
Abychom program spustili, musíme ještě upravit hlavičku metody
main() a nebo použít bloky try-catch. My zde jen
narychlo upravíme hlavičku:
public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
Když kód nyní spustíme, aplikace nám vyhubuje, že se snažíme přistoupit k privátnímu atributu:
Konzolová aplikace
Exception in thread "main" java.lang.IllegalAccessException: class cz.itnetwork.reflexe.Main cannot access a member of class cz.itnetwork.reflexe.Kostka with modifiers "private"
To je zcela v pořádku. Za normálních okolností není dobrý nápad upravovat privátní atributy, nikdy nemůžete tušit, jakou škodu tím napácháme.
Když jsme si ale zcela jisti, co děláme, použijeme před
setterem magickou metodu setAccessible(), pomocí které
"zpřístupníme" atribut pomocí reflexe. Od této chvíle bude možné atribut
upravovat, ovšem pouze v rámci reflexe nad konkrétní instancí třídy
Field.
Aktualizovaný kód bude vypadat následovně:
Kostka kostka = new Kostka(); System.out.println("Originální počet stěn: " + kostka.vratPocetSten()); final Field pocetStenField = Kostka.class.getDeclaredField("pocetSten"); pocetStenField.setAccessible(true); pocetStenField.set(kostka, 12); System.out.println("Nový počet stěn: " + kostka.vratPocetSten());
Nyní již bude vše v pořádku a hodnota atributu bude úspěšně změněna:
Konzolová aplikace
Originální počet stěn: 6
Nový počet stěn: 12
Touto cestou lze dokonce změnit i atribut označený
klíčovým slovem final (konstantu)!
Výjimky při práci s reflexí
Pokud pouze získáváme informace o třídě, tak žádné výjimky řešit
nemusíme. Pokud ale začneme získávat konkrétní metody/atributy pomocí
metod getMethod(String name) nebo
getField(String name), již budeme muset ošetřit případ, kdy
metoda nebo atribut nebudou existovat. To samé platí při přepisování
hodnot atributů. Výjimky se probírají v kurzu Soubory
v Javě.
