FK~

Moje odkazy

Ostatní odkazy

EFF: svoboda blogování
Close Windows
Nenajdete mě na Facebooku ani Twitteru
Rozpad EU
Jsem členem FSF
Jsem členem EFF

Přepisujeme soukromé proměnné v Javě pomocí reflexe

vydáno: 13. 6. 2015 22:56, aktualizováno: 14. 6. 2015 12:40

Java je (primárně) objektově orientovaný jazyk a máme zde zapouzdření – k soukromým (privátním) proměnným cizího objektu nemůžeme přímo přistupovat – objekt si je může měnit jen sám a ostatním to může dovolit jen nepřímo přes svoje metody. Přesto Java nabízí způsob, jak zapouzdření obejít – reflexe.

Logo OpenJDK (průhledné 1)

Přístup k proměnné přes reflexi

K polím/proměnným (stejně jako k metodám, konstruktorům atd.) se dostaneme přes objekt java.lang.Class reprezentující příslušnou třídu. Kód si zaobalíme do metody:

private static void nastav(Object objekt, String proměnná, Object hodnota)
	throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {

	Field f = objekt.getClass().getDeclaredField(proměnná);
	f.setAccessible(true);
	f.set(objekt, hodnota);

	// TODO: můžeme odchytávat výjimky a vyhazovat vlastní výjimku případně běhovou

}

Potom můžeme místo standardního:

NějakáTřída t = NějakáTřída();

t.setPromenna(x);
// případně:
t.verejnaPromenna = x;

napsat:

nastav(t, "proměnná", x); // ne "setProměnná", protože nejdeme přes metodu
// případně:
nastav(t, "veřejnáProměnná", x);

Což nám dává stejné možnosti jako dynamické programovací jazyky. Samozřejmě i se všemi nevýhodami (překlepy neodhalené kompilátorem, nečitelnost, neudržovatelnost, nemožnost refaktorovat kód…).

Reflexe vs. volání metody

Něco na způsob polské notace :-)

Rychlost reflexe

Že je reflexe pomalá, je známá věc. Ale o kolik je pomalejší? Napsal jsem si jednoduchý test, který různými způsoby nastavuje proměnnou objektu a měří časy. Test jsem pustil několikrát a hodnoty se různí, ale pro hrubou představu – tohle jsou typické časy:

     přímý přístup:  celkovýČas =        5 ms relativníČas =   100.00 %
            setter:  celkovýČas =        5 ms relativníČas =   100.00 %
           reflexe:  celkovýČas =      151 ms relativníČas =  3020.00 %
reflexe připravená:  celkovýČas =       16 ms relativníČas =   320.00 %

Absolutní časy jsou pro milion opakování – zajímavější jsou ale relativní časy, zpomalení.

Přístup přes setter je srovnatelný s přímým přístupem k veřejné proměnné. Někdy byl dokonce rychlejší (náhoda).

Nastavení hodnoty pomocí reflexe v případě, že pokaždé znovu hledáme pole (java.lang.reflect.Field) dané třídy podle názvu (viz metoda nastav() výše) je cca 30× pomalejší.

Pokud ale pole ve třídě najdeme jen jednou (což pak můžeme aplikovat na různé instance dané třídy – Field se váže ke třídě nikoli konkrétní instanci), tak už je čas jen 3× horší než přímý přístup nebo setter.

Třikrát nebo dokonce třicetkrát horší čas vypadá na první pohled hrozně, ale ve výsledku to může být zanedbatelné – pod hranicí měřitelnosti. Program totiž typicky stráví o X řádů víc času výpočtem hodnot nebo jejich získáváním a odesíláním (síť, databáze, disk atd.), než jejich nastavováním do proměnné objektu.

Kdy použít reflexi

Troufám si tvrdit, že reflexe do kódu aplikací nepatří. Nejde až tak o výkon, jako spíš čitelnost a udržovatelnost. Kvůli reflexi se připravíte o pohodlí a spolehlivost dané typovou kontrolou (jedna z největších výhod oproti dynamickým jazykům).

Přesto se najdou případy, kdy dává reflexe smysl:

  • frameworky a knihovny: když už musíte reflexi použít, izolujte ji do frameworku nebo knihovny; reflexe by neměla být rozlezlá všude možně ve vašem aplikačním kódu a neměli byste s ní v aplikaci přímo pracovat; možné využití:
    • mapování – obecné nebo deklarativní (anotace) mapování tříd na okolní svět (databáze, XML nebo jiný formát atd.)
    • konverze objektů – opět obecné nebo deklarativní mapování, tentokrát mezi různými třídami – můžeme např. filtrovat pole (některá vynechat) nebo měnit datové typy či provádět další transformace
    • editory – obecné GUI pro práci s objekty (JavaBean)
  • jednotkové testy: při psaní testů pro třídy, do kterých se injektují další objekty (např. v Javě EE nebo ve Springu) narazíte na problém, že požadovaná hodnota chybí – pomocí standardního volání metod ji tam v testu nedostanete; můžete si buď dopsat pomocné settery (což ale znečišťuje veřejné API vaší třídy – z hlediska obchodní logiky tam tyto metody nemají co dělat a budete k nim muset psát komentáře typu „pozor, jen pro testy, nevolat přímo z aplikace!!!“) nebo použijete právě reflexi a uděláte to, co dělá ta Java EE nebo Spring při spouštění vaší aplikace

I v těchto případech je ale dobré s reflexí šetřit a používat ji s rozumem. Také zvažte použití již existující knihovny než začnete psát vlastní.

Odkazy a zdroje:

Témata: [softwarové inženýrství] [Java]

Komentáře čtenářů

Tento článek zatím nikdo nekomentoval

Přidat komentář

reagujete na jiný komentář (zrušit)
jméno nebo přezdívka
název příspěvku
webová stránka, blog
e-mailová adresa
nápověda: možnosti formátování
ochrana proti spamu a trollům

Náhled komentáře