Főoldal

"Mérnököt a mérnöktől"

A Schönherz Bázis összeköti az állást kereső és állást kínáló mérnököket.

CV küldés

Küldj önéletrajzot! Gyorsan, egyszerűen.
Megjegyzésbe írd be a pozíció nevét.
CV küldés

Iratkozz fel hírlevelünkre!

Hírek

Metóduskezelés Java-ban
Metóduskezelés Java-ban

Legfrissebb cikkünkben ezúttal egy fontos API-t mutatunk be, amely a Java 7-ben mutatkozott be, majd a további verziókban jelentős fejlődésen ment keresztül. Ez nem más, mint a java.lang.invoke.MethodHandles.


Hogy mik a metóduskezelők? Az API dokumentációja szerinti definíció a következő:

A metóduskezelő egy típusos, közvetlenül végrehajtható referencia egy tagfüggvényre, konstruktorra, mezőre vagy hasonló, alacsony szintű operációra, a bemenő vagy visszaadott értékékek változtatásának lehetőségével. Egyszerűbben szólva, a metóduskezelők alacsony szintű megoldások a tagfüggvények keresésére, alkalmazására és meghívására.


A metóduskezelők megváltoztathatatlanok és nincs látható állapotuk. Létrehozásukhoz négy lépés szükséges:

1. A feloldó (lookup) létrehozása

2. A metódus típusának meghatározása

3. A metóduskezelő megkeresése

4. A metóduskezelő meghívása


Metóduskezelők vs Reflection


A metóduskezelőket azért vezették be, hogy a már létező java.lang.reflect API mellett működjenek, mivel különböző célokat különböző módon szolgálnak. Teljesítmény szempontjából a MethodHandles API bír előnnyel, hiszen a hozzáférési ellenőrzések létrehozáskor történnek meg, szemben a Reflection API futási idejű ellenőrzésével. Ez a különbség határozottan kijön, ha egy biztonsági menedzser is jelen van, hiszen a tag- és osztályfeloldások is biztonsági ellenőrzésekkel terheltek.


Azonban a teljesítmény csak egy lehetséges mutató. Figyelembe kell vennünk, hogy a MethodHandles-t nehezebb használni, mivel híján van az olyan eszközönek, mint például a tagosztályok enumerációja, hozzáférhetőségi jelzők ellenőrzése és egyebek. Ennek ellenére a metóduskezelőkkel lehetőségünk van a metódusok felbontására (currying), a paraméterek típusának vagy sorrendjének megváltoztatására. Most, hogy értjük a MethodHandles API célját és alapjait, nekiláthatunk a használatuknak, a feloldóval kezdve.


A feloldó (lookup) létrehozása


Az első teendő egy metóduskezelő létrehozásakor a lookup megszerzése. Ez az a gyári objektum, amely a tagfüggvények, konstruktorok és mezők feloldó osztály számára látható metóduskezelőinek létrehozásáért felel.


A MethodHandles API által megalkothatunk egy lookup objektumot, különböző hozzáférhetőségi módokkal.


Hozzunk létre egy feloldót, amely publikus tagfüggvényekhez fér hozzá:


MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();


Ha private vagy protected tagfüggvényekhez is hozzá akarunk férni, használjuk a lookup() metódust:


MethodHandles.Lookup lookup = MethodHandles.lookup();


MethodType létrehozása


Ahhoz, hogy létre tudjunk hozni egy MethodHandle-t, a feloldó objektumnak szüksége van a típusára, ezt pedig a MethodType osztály intézi el. Konkrétabban, a MethodType reprezentálja a metóduskezelő és az őt hívó objektum által átadott argumentumok és visszaadott értékek típusát.


A MethodType szerkezete egyszerű: a visszatérési érték típusából és megfelelő számú paraméter-típusból áll, melyeknek a hívó objektumokkal összhangban kell állnia. A MethodHandle-höz hasonlóan a MethodType példányai is megváltoztathatatlanok. Lássuk, hogy definiálhatunk egy MethodType-ot, amely egy java.util.List osztályt ad vissza és egy Object tömböt vár:


MethodType mt = MethodType.methodType(List.class, Object[].class);


Abban az esetben, ha a metódus egy alaptípust vagy void-ot ad vissza, a nekik megfelelő osztályt kell használnunk (void.class, int.class ...)

Hozzunk létre egy MethodType-ot, ami egy int értékkel tér vissza és egy Objectet fogad:


MethodType mt = MethodType.methodType(int.class, Object.class);


Most már létrehozhatjuk a MethodHandle-t...


 A MethodHandle megkeresése


Miután definiáltuk a MethodType-ot, a MethodHandle létrehozásához meg kell keresnünk a lookup vagy a publicLookup objektum segítségével, megadva az ősosztályt és a metódus nevét is.


Alapesetben a lookup függvények csokrával segíti, hogy alkalmas módon, az objektumunk hatókörét figyelembe véve találjuk meg a metóduskezelőt. A legegyszerűbb esettel kezdve fedezzük fel az alapvető eseteket.


Metóduskezelő tagfüggvényekhez


A findVirtual() függvény használatával létrehozhatunk egy MethodHandle-t egy objektum tagfüggvényéhez. Hozzunk létre egyet a String osztály concat() függvényének.


MethodType mt = MethodType.methodType(String.class, String.class);

MethodHandle concatMH = publicLookup.findVirtual(String.class, "concat", mt);


Metóduskezelő statikus függvényekhez


Ha egy statikus metódushoz szeretnénk hozzáférni, a findStatic() függvényt alkalmazhatjuk:


MethodType mt = MethodType.methodType(List.class, Object[].class);

 

MethodHandle asListMH = publicLookup.findStatic(Arrays.class, "asList", mt);


Ebben a konkrét esetben létrehoztunk egy kezelőt, ami egy Object-ekből álló tömböt Object-ekből álló listává alalkít.


Metóduskezelő konstruktorokhoz.


A findConstructor() metódussal érhetünk el egy konstruktort.


Hozzunk létre egy kezelőt, ami az integer osztály konstruktoraként szolgál, String attribútumot fogadva:


MethodType mt = MethodType.methodType(void.class, String.class);

 

MethodHandle newIntegerMH = publicLookup.findConstructor(Integer.class, mt);


Metóduskezelő mezőkhöz


Metóduskezelőkkel mezőket is elérhetünk. Kezdjük a Book osztály definiálásával:


public class Book {

  

  String id;

  String title;

 

  // constructor

 

}


Ha a metóduskezelő közvetlenül hozzáfér a mezőhöz, akkor egy getterként viselkedő kezelőt is létrehozhatunk:


MethodHandle getTitleMH = lookup.findGetter(Book.class, "title", String.class);


A változók/mezők kezeléséről további információ itt nyerhető, ahol a Java 9-ben megjelenő java.lang.invoke.VarHandle API-t elemezzük.


Metóduskezelő privát metódusokhoz


Privát metódusokhoz a java.lang.reflect API-val férhetünk hozzá. Először adjunk hozzá a Book osztályhoz egy private tagfüggvényt:


private String formatBook() {

  return id + " > " + title;

}


Most már létrehozhatunk egy kezelőt, ami úgy viselkedik, mint a formatBook() metódus:


Method formatBookMethod = Book.class.getDeclaredMethod("formatBook");

formatBookMethod.setAccessible(true);

 

MethodHandle formatBookMH = lookup.unreflect(formatBookMethod);


Egy MethodHandle meghívása


Ha már létrehoztunk egy metóduskezelőt, a következő lépésben használjuk is. A MethodHandle osztály háromféle lehetőséget ad egy metóduskezelő meghívására: invioke(), invokeWithArguments(), és invokeExact(). Kezdjük az invoke() lehetőséggel!


AZ invoke() használata során kikényszerítjük a a megfelelő számú argumentum alkalmazását, de megengedjük a típuskonverziót (casting, boxing/unboxing) a bemenő argumentumokra és a visszatérési értékekre.


Lássuk, hogyan használható az invoke() típuskonverzión áteső (boxed) argumentumokra:


MethodType mt = MethodType.methodType(String.class, char.class, char.class);

MethodHandle replaceMH = publicLookup.findVirtual(String.class, "replace", mt);

 

String output = (String) replaceMH.invoke("jovo", Character.valueOf('o'), 'a');

 

assertEquals("java", output);


Ebben az esetben a replaceMH() char értékeket vár, az invoke() pedig típuskonverziót (unboxing) végez a Character paraméteren a végrehajtás előtt.

Meghívás argumentumokkal


Az invokeWithArguments() a legmegengedőbb módszer a három közül a kezelők meghívására. Valójában így lehetséges különböző számú paraméterrel és típuskonverzióval meghívni egy kezelőt.


A gyakorlatban például így hozhatunk létre egy Integer List-et, egy int tömb alapján:


MethodType mt = MethodType.methodType(List.class, Object[].class);

MethodHandle asList = publicLookup.findStatic(Arrays.class, "asList", mt);

 

List<Integer> list = (List<Integer>) asList.invokeWithArguments(1,2);

 

assertThat(Arrays.asList(1,2), is(list));


Meghívás pontosan


Ha szigorúbban akarjuk végrehajtani a metóduskezelőnket (argumentumok típusa és száma), az invokeExact() metódust kell használnunk. Ez semmilyen típuskonverziót nem eszközöl és fix számú argumentumot vár.


Lássuk, hogy adhatunk össze két int értéket metóduskezelőkkel:


MethodType mt = MethodType.methodType(int.class, int.class, int.class);

MethodHandle sumMH = lookup.findStatic(Integer.class, "sum", mt);

 

int sum = (int) sumMH.invokeExact(1, 11);

 

assertEquals(12, sum);


Ebben az esetben, ha egy inttől különböző típusú változót adunk át az invokeExact() függvénynek, egy WrongMethodTypeException lesz az eredmény.


Tömbök kezelése


A metóduskezelők nem csak mezők vagy objektumok kezelésére alkalmasak, hanem tömbökkel is használhatók, például az asSpreader() API-val készíthetünk egy tömb-terítő metóduskezelőt.


Jelen esetben a metóduskezelő egy tömb argumentumot fogad, az elemeit pedig különálló argumentumokká konvertálja, esetleg a tömb hosszát is hozzáveszi.

Lássuk, hogyan ellenőrizhetjük, hogy a tömb kiterített elemei egyenlők-e:


MethodType mt = MethodType.methodType(boolean.class, Object.class);

MethodHandle equals = publicLookup.findVirtual(String.class, "equals", mt);

 

MethodHandle methodHandle = equals.asSpreader(Object[].class, 2);

 

assertTrue((boolean) methodHandle.invoke(new Object[] { "java", "java" }));


Metóduskezelők kiegészítése


Miután definiáltunk egy metóduskezelőt, kiegészíthetjük úgy, hogy meghívás nélkül csatolunk hozzá egy argumentumot. Például a Java 9-ben ez a Stringek összefűzésére használatos. Lássuk, hogy hajhatjuk végre az összefűzést, egy utótag concatMH mögé tűzésével:


MethodType mt = MethodType.methodType(String.class, String.class);

MethodHandle concatMH = publicLookup.findVirtual(String.class, "concat", mt);

 

MethodHandle bindedConcatMH = concatMH.bindTo("Hello ");

 

assertEquals("Hello World!", bindedConcatMH.invoke("World!"));


Java 9 fejlesztések


A Java 9-ben a MethodHandles API használatát könnyebbé tevő fejlesztések jelentek meg.


Ezek három fő területet érintettek:


1. Lookup függvények - különböző kontextusból is elérhető osztályfeloldók és nem absztrakt függvények támogatása az interfészekben

2. Argumentum-kezelés - fejlesztett hajtogatás (folding), gyűjtés (collecting), terítés(spreading) képességek

3. További kombinációk - további ciklus típusok (loop, whileLoop, doWhileLoop...) és fejlesztett kivételkezelés a tryFinally használatával


Ezek a változások néhány további előnnyel is járnak: hatékonyabb JVM fordító optimalizálás, példányosítás-redukció, a MethodHandles precízebb használatát teszi lehetővé.


 Részletekért lásd a MethodHandles API Javadoc dokumentumot.


(Forrás)


***

Ha Te is kreatív, kihívásokkal teli mérnök állást keresel minõségi munkáltatónál, jó helyen jársz, mert a Schönherz Bázis épp azért jött létre, hogy Neked segítsen.
Gyere, nézz szét aktuális állásaink között!