Java Reflection Reflexió a Java nyelvben 1
A Java vizsgálja a Java-t A Java egyik szokatlan képessége, hogy egy program vizsgálhatja önmagát» Meghatározhatjuk egy objektum osztályát» Mindent megtudhatunk egy osztályról: a hozzáférési módosítóit, a szülőosztályát, mezőit, konstruktorait és metódusait» Kideríthetjük, egy interfészben mi micsoda» Még ha nem is ismerjük a dolgok neveit a program megírásakor, Létrehozhatjuk egy osztály egy példányát Beállíthatunk egy lekérdezhetünk példányváltozókat Meghívhatjuk egy objektum egy metódusát Létrehozhatunk és kezelhetünk tömböket 2
Mire való a reflexió? A normális programokban nincs szükség reflexióra Akkor van szükség a reflexióra, ha programokat feldolgozó programokkal dolgozunk Jellegzetes példák:» Osztályböngésző» Debugger» GUI építő» IDE, pl. BlueJ, Netbeans vagy Eclipse» Egy hallgatói programokat osztályozó program 3
A Class osztály Hogy többet tudjunk meg egy osztályról, először a Class objektumára van szükségünk» Ha van egy obj objektumunk, a Class c = obj.getclass(); utasítással kaphatjuk meg az osztályobjektumát.» Egy c osztály szülőosztályának osztályobjektumát a Class sup = c.getsuperclass(); utasítással kaphatjuk meg.» Ha ismerjük egy osztály nevét (pl. Button) fordítási időben, a Class c = Button.class; utasítással is megkaphatjuk.» Ha tudjuk az osztály nevét futási időben (egy str String változóban), az osztályobjektumát a Class c = Class.forName(str); utasítás adja. 4
Az osztálynév lekérése Ha van egy c osztályobjektumunk, a c.getname() utasítással kaphatjuk meg az osztály nevét. A getname a teljes minősített nevet adja vissza, tehát a Class c = Button.class; String s = c.getname(); System.out.println(s); kimenete a következő lesz: java.awt.button A Class osztály és metódusai a java.lang csomagban vannak, ami mindig importálásra kerül és elérhető 5
Kérjük le az összes ősosztályt A getsuperclass() egy Class objektumot ad vissza (vagy null-t ha Object-re hívjuk meg, aminek nincs ősosztálya) A következő kód a Sun útmutatóból van: static void printsuperclasses(object o) { Class subclass = o.getclass(); Class superclass = subclass.getsuperclass(); while (superclass!= null) { String classname = superclass.getname(); System.out.println(className); subclass = superclass; superclass = subclass.getsuperclass(); } } 6
Kérjük le az osztály módosítóit I Egy Class objektum módosítói (pl. public, final, abstract, stb.) egy int-ben vannak lekódolva, és a getmodifiers() metódussal kérdezhetőek le. Az int eredmény dekódolásához a java.lang.reflect csomagban lévő Modifier osztály metódusaira van szükségünk, tehát: import java.lang.reflect.*; Ezután már csinálhatunk ilyesmi dolgokat: if (Modifier.isPublic(m)) System.out.println("public"); 7
Kérjül le az osztály módosítóit II A Modifier (többek között) a következő metódusokat tartalmazza:» public static boolean isabstract(int)» public static boolean isfinal(int)» public static boolean isinterface(int)» public static boolean isprivate(int)» public static boolean isprotected(int)» public static boolean ispublic(int)» public static String tostring(int) Ez egy ilyen típusú String-et fog visszaadni: "public final synchronized strictfp" 8
Kérjünk le interfészeket Egy osztály nulla vagy több interfészt implementálhat A getinterfaces() Class objektumok egy tömbjét adja vissza Példa: static void printinterfacenames(object o) { Class c = o.getclass(); Class[] theinterfaces = c.getinterfaces(); for (Class inf: interfaces) { System.out.println(inf.getName()); }} Jegyezzük meg, milyen előnyös a bejáró forciklus 9
Osztályok és interfészek vizsgálata A Class osztály osztályokat és interfészeket is reprezentál Ahhoz, hogy eldöntsük, egy adott Class objektum interfész-e, használjuk a c.isinterface() metódust Ha többet akarunk megtudni az osztályobjektumról, használjuk a következőket:» getmodifiers()» getfields() // mezők, azaz példányváltozók» getconstructors()» getmethods()» isarray() 10
Kérjünk le mezőket public Field[] getfields() throws SecurityException» A publikus mezők egy tömbjét adja vissza (az örökölt mezőket is beleértve).» A tömb elemszáma lehet nulla» A mezők visszaadása nem meghatározott sorrendben történik» A helyben definiált és az örökölt példányszintű változók is visszaadódnak, de a statikus változók nem. public Field getfield(string name) throws NoSuchFieldException, SecurityException» A megnevezett publikus mező adódik vissza» Ha közvetlenül nem találunk ilyen mezőt, a szülőosztályokban és interfészekben keresünk rekurzívan 11
Mezők használata I Ha f egy mező objektum, akkor» f.getname() a mező egyszerű nevét» f.gettype() a mező típusát (osztályát)» f.getmodifiers() a mező módosítóit» f.tostring() a mező hozzáférési módosítóit, típusát és teljesen minősített nevét tartalzó String-et adja vissza Példa: public java.lang.string Person.name» f.getdeclaringclass() azt az osztályt adja vissza, mellyel a mező deklarálásra került megjegyzés: a getfields() visszaadhat ősosztály mezőket is. 12
Mezők használata II Egy konkrét obj objektum mezőit a következőkkel érhetjük el:» boolean f.getboolean(obj), int f.getint(obj), double f.getdouble(obj), stb., a mező értékét adják vissza, feltételezve, hogy adott típusú, vagy az adott típusra bővíthető» Object f.get(obj) úgy annak feltételezésével adja vissza a mező értékét, hogy egy Object» void f.set(obj, value), void f.setboolean(obj, bool), void f.setint(obj, i), void f.getdouble(obj, d), stb. beállítják a mező értékét 13
Kérjük le egy osztály konstruktorait Ha c egy Class, akkor c.getconstructors() : Constructor[] alakban visszaadja a c osztály összes publikus konstruktorának egy tömbjét. c.getconstructor( Class paramtypes ) egy olyan konstruktort ad vissza, mely paramétereinek típusai egyeznek a megadott paramtypes típusokkal. Példa: String.class.getConstructors().length > 15; String.class.getConstrucor( char[].class, int.class, int.class).tostring() > String(char[], int,int). 14
Konstruktorok Ha a c egy Constructor objektum, akkor» c.getname() a konstruktor nevét adja vissza Stringként (ez megegyezik az osztály nevével)» c.getdeclaringclass() azt az osztályt (Class-t) adja vissza, melyben ez a konstruktor deklarálva lett» c.getmodifiers() a konstruktor módosítóit (Modifier-eit) adja vissza» c.getparametertypes() Class objektumok egy tömbjét adja vissza deklarációs sorrendben» c.newinstance(object initargs) létrehozza és visszaadja a c osztály egy új példányát Az olyan argumentumok, melyeknek primitív típusúnak kell lennie, szükség esetén automatikusan kicsomagolásra kerülnek 15
Példa Constructor c = String.class.getConstrucor( char[].class, int.class, int.class); c.tostring() String(char[], int,int). String s = c.newinstance( assert s == bc ; new char[] { a, b, c, d }, 1, 2 ); 16
Methods public Method[] getmethods() throws SecurityException» Method objektumok egy tömbjével tér vissza» Ezek az osztály vagy interfész publikus tag metódusai, beleértve az örökölt metódusokat» A metódusok nem meghatározott sorrendben kerülnek visszaadásra public Method getmethod(string name, Class parametertypes) throws NoSuchMethodException, SecurityException 17
Method metódusok, I getdeclaringclass()» Azt a Class objektumot adja vissza, mely reprezentálja az adott Method objektum által reprezentált metódust deklaráló osztályt vagy interfészt getname()» Az adott Method objektum által reprezentált metódus nevét adja vissza egy String formájában getmodifiers()» Az adott Method objektum által reprezentált metódus Java nyelvi módosítóit adja vissza integerként getparametertypes()» Azon Class objektumok tömbjét adja vissza deklarációs sorrendben, melyek reprezentálják az adott Method objektum által reprezentált metódus formális paramétereinek típusait 18
Method metódusok, II getreturntype()» Egy, az adott Method objektum által reprezentált metódus formális visszatérési típusát reprezentáló Class objektumot ad vissza tostring()» Egy String-et ad vissza, mely leírja a metódust (általában elég hosszú) public Object invoke(object obj, Object args)» Meghívja az adott Method objektum mögött meghúzódó metódust a megadott objektumra a megadott paraméterekkel» Az egyes paraméterek automatikusan kicsomagolásra kerülnek, hogy illeszkedjenek a primitív típusú formális paraméterekre 19
abcdefg.length() > 7 invoke() példák Method lengthmethod = String.class.getMethod( length ) ; lengthmethod.invoke( abcdefg ) > 7 abcdefg.substring(2, 5) > cde Method substringmethod = String.class.getMethod ( substring, int.class, Integer.TYPE ) ; substringmethod.invoke( abcdefg, 2, new Integer(5) ) > cde 20
Tömbök I Annak meghatározásához, hogy egy adott obj objektum tömb-e,» Kérdezzük le az osztályát, c-t: Class c = obj.getclass();» Ezt teszteljük a c.isarray() metódushívással A tömb komponenseinek típusát adja meg a» c.getcomponenttype() Ez null-t ad vissza, ha c nem egy tömb osztálya Példa:» int[].class.isarray() == true ;» int[].class.getcomponenttype() == int.class 21
Tömbök II A java.lang.reflect csomag Array osztálya statikus metódusokat biztosít ahhoz, hogy tömbökkel tudjunk dolgozni Bármilyen tömb létrehozásához: Array.newInstance(Class componenttype, int size)» Ez egy Object-et, az újonnan létrejövő tömböt adja vissza Ezt tetszőlegesen bármilyen típusra lehet kasztolni» Akár maga a componenttype is lehet egy tömb Így egy többdimenziós tömböt hoznánk létre Általában a dimenziók számának határa 255 Array.newInstance(Class componenttype, int sizes)» Ez egy Object-ként egy újonnan létrejövő többdimenziós tömböt ad vissza (sizes.length darab dimenzióval) 22
Példák A következő két objektum típusa megegyezik:» new String[10]» Array.newInstance(String.class, 10) A következő két objektum típusa megegyezik:» new String[10][20]» Array.newInstance(String.class, 10, 20) 23
Tömbök III Tömbelemek értékének kinyeréséhez» Array.get(Object array, int index) egy Object-et» Array.getBoolean(Object array, int index) egy boolean-t» Array.getByte(Object array, int index) egy byte-ot» ad vissza, és így tovább Ha pedig értékeket akarunk letárolni egy tömbben,» Array.set(Object array, int index, Object value)» Array.setInt(Object array, int index, int i)» Array.setFloat(Object array, int index, float f)» stb. 24
Példák a = new int[] {1,2,3,4}; Array.getInt(a, 2) // 3 Array.setInt(a, 3, 5 ) // a = {1,2,3, 5 }. s = new String[] { ab, bc, cd }; Array.get(s, 1 ) // bc Array.set(s, 1, xxx ) // s[1] = xxx 25
Kérjük le egy osztály nem publikus tagjait A fentebb említett Class minden getxxx() típusú metódusa csak a célosztály (és annak ősosztályainak) publikus tagjait adja vissza, de nem tudják a nem publikus tagokat visszaadni. A Class-ben létezik egy másik csoport, a getdeclaredxxx() metódusok, melyek a célosztály összes tagot (még a privát és statikus módosítójúakat is) visszaadják, az örökölt tagokat viszont nem. getdeclaredconstructors(), defdeclaredconstrucor(class ) getdeclaredfields(), getdeclaredfield(string) getdeclaredmethods(), getdeclaredmethod(string, Class ) 26
Példa String.class.getConstructors().length > 15 String.class.getDeclaredConstructors().length > 16. Constructor[] cs = String.class.getDeclaredConstructors(); for(constructor c : cs) if(! (Modifier.isPublic(c.getModifiers()))) out.println(c); > java.lang.string(int,int,char[]) // package 27
Összefoglaló megjegyzések Az említett metódusok közül sok olyan kivételeket válthat ki, melyekre itt nem tértünk ki» Részletek a Java API-ban találhatóak erről A reflexiót nem használjuk normális programokban, de amikor szükség van rá, akkor pótolhatatlan A Java reflection csomagjának tanulmányozásával lehetőség nyílik a java osztályszerkezetének áttekintésére. 28
3. Programozási feladat Írjunk egy DumpClass nevű programot, mely szépen ki tud írni egy adott típust (osztályt vagy interfészt) részletesen, a javap outputjához hasonlóan, azzal a kivétellel, hogy: Megjegyzések: 1. A kimenet első sora egy csomagdeklaráció legyen. 2. Minden, a kimenetben használt nevet importálni kell, így csak egyszerű nevek szerepelhetnek az import utasításokon kívül.» példa: a következő kimenet helyett:» public class packageofa.a {» public java.util.vector m1() }» A következő kimenetet kell kapnunk:» package packofa;» import java.util.vector;» public class A {» public java.util.vector m1() } 29