NOVINKA: Začni v IT jako webmaster s komplexním akreditovaným online kurzem Tvůrce WWW stránek. Zjisti více:
NOVINKA: Staň se datovým analytikem a získej jistotu práce, lepší plat a nové kariérní možnosti. Více informací:

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í hodnotu true, pokud se jedná o rozhraní, jinak hodnotu false.
  • isEnum() - Vrátí jodnotu true, pokud se jedná o enum, jinak hodnotu false.

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() a
  • getAnnotations().

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ě.


 

Všechny články v sekci
Objektově orientované programování v Javě
Článek pro vás napsal Petr Štechmüller
Avatar
Uživatelské hodnocení:
554 hlasů
Autor se věnuje primárně programování v Javě, ale nebojí se ani webových technologií.
Aktivity