:: Enseignements :: ESIPE :: E4INFO :: 2020-2021 :: Java Inside ::
![[LOGO]](http://igm.univ-mlv.fr/ens/resources/mlv.png) |
Invocation API java.lang.invoke
|
Lookup, MethodHandle
Exercice 1 - MethodHandle
Le but de cet exercice est se familiariser avec la notion the method handle.
En Java, il existe deux APIs pour effectuer des appels de méthode de façon
réflexif, l'API de reflection (java.lang.reflect) disponible
depuis la version 1.1 de Java et l'API d'invocation (java.lang.invoke)
disponible depuis la version 7 de Java.
L'API d'invocation est plus bas niveau que l'API de reflection que nous avons vu au lab
précédent. Elle est écrite en style fonctionnel, un
MethodHandle n'est pas mutable,
donc faire des conversions, une opérations sur un
MethodHandle veut dire
re-créer un nouveau
MethodHandle.
L'API d'invocation founie
-
Une classe MethodHandle qui correspond à un pointeur de fonction typé
à l'exécution. N'imporet quelle méthode existante, static ou d'instance, est un
MethodHandle.
Donc, comme en Python ou JavaScript, si l'on se trompe dans la signature des méthodes,
les erreurs auront lieu à l'exécution et pas à la compilation.
-
Trois façons d'appeler un MethodHandle
-
L'invocation exacte en utilisant la méthode .invokeExact(), dans ce cas
les arguments d'appel et les paramètre de la méthode doivent être identique
(aucune conversion ne sera faite). En terme de performance c'est la plus efficace.
-
L'invocation "comme en Java" en utilisant la méthode .invoke, dans ce cas
les conversions habituelles sont autorisées (type primitive, sous-typage, boxing, varargs).
En terme de performance, c'est plus lent car cela demande à la VM de trouver la bonne
conversion à l'exécution (donc de faire des tests dynamiques).
-
L'invocation "comme java.lanf.reflect.Method.invoke" en utilisant la méthode
.invokeWithArguments() qui prend en paramètre soit un tableau soit une liste,
il y a deux surcharges. En terme de performance, c'est bien sûr, l'appel le plus couteux.
-
Un objet Lookup (son vrai nom est MethodHandles.Lookup) qui permet
de créer des pointeurs de fonction (MethodHandle) à partir de méthode déclarées,
par exemple, findStatic permet de chercher une méthode statique dans une classe,
findVirtual permet de chercher une méthode d'instance dans une classe.
-
Un ensemble de méthode à l'ordre supérieur sur des MethodHandle
(donc des méthodes qui prennent en paramètre un ou plusieurs MethodHandles
et qui renvoie un MethodHandle agissant comme des opérateurs capable
de combiner des MethodHandle.
Ces méthodes à l'ordre supérieur sont
-
soit des méthodes d'instance de MethodHandle
-
soit des méthodes statiques de la classe MethodHandles (noté le 's' à la fin)
Ces méthodes offrent des opérations comme
-
MethodHandles.insertArguments() qui permet d'insérer une valeur à la place d'un paramètre
à un MethodHandle existant, en renvoyant un nouveau MethodHandle
avec un paramètre de moins.
-
MethodHandles.dropArguments() qui permet de supprimer la valeur d'un argument
en renvoyant un nouveau MethodHandle avec un paramètre supplémentaire.
-
MethodHandle.asType() qui prend en paramètre un type de fonction MethodType
et renvoie un MethodHandle qui va convertir ses arguments pour pouvoir appeler
le MethodHandle sur lequel asType a été apellé.
-
MethodHandles.constant() qui permet de créer un MethodHandle qui renvoie
toujours une constante
-
MethodHandles.guardWithTest() qui prend trois MethodHandles en paramètre et
renvoie un MethodHandle qui si il est appelé, appel le premier MethodHandle
qui doit renvoyer un boolean et en fonction de la valeur du booléen appel soit le deuxiéme
MethodHandle si le booléen est vrai ou le troisième MethodHandle si le
booléen est faux agissant comme une sorte de if.
-
Dans le répertoire java-inside, créer un sous-répertoire lab3,
recopier dans celui-ci le fichier POM du lab2 et changer le contenu du
POM de java-inside et du lab3 pour indiquer que le lab3
est un sous module.
-
Dans une classe de test JUnit 5 MethodHandleTests, écrire un test nommé
findStaticTest permettant
de récupérer un MethodHandle correspondant à la méthode statique
Integer.parseInt et vérifiant que la valeur renvoyée par la méthode
MethodHandle.type()
appelée sur le MethodHandle créé est correcte.
Pour cela, il vous faut, dans un premier temps, créer un objet
Lookup
en utilisant la méthode statique
MethodHandles.lookup()
puis sur cette objet de type Lookup
utiliser la méthode
Lookup.findStatic()
-
Ajouter un nouveau test nommé findVirtualTest qui permet d'obtenir
un MethodHandle sur la méthode d'instance String.toUpperCase().
Vérifier, de même, la valeur de retour de MethodHandle.type().
-
Ajouter maitenant deux nouveaux tests invokeExactStaticTest et
invokeExactStaticWrongArgumentTest. Le premier doit appeler
un MethodHandle créer sur Integer.parseInt et vérifier que
l'appel en utilisant invokeExact() appel bien la méthode Integer.parseInt.
Le second test, doit aussi appeler MethodHandle créer sur Integer.parseInt
mais en oubliant de passer la chaine de caractère en argument et vérifier que
la bonne exception en renvoyée
Note: pour trouver la bonne exception, RTFM
RTFM !
-
Ajouter deux nouveaux tests invokeExactVirtualTest et
invokeExactVirtualWrongArgumentTest qui marche comme les deux tests précédents
mais test l'appel à la méthode d'instance String.toUpperCase.
-
Ajouter deux nouveaux tests, invokeStaticTest et invokeVirtualTest,
contenant chacun un appel à assertEquals et un autre à assertThrows
executer en utilisant un assertAll,
qui testent que l'on peut appeler les méthodes handles utilisant
invoke.
Avec pour le premier test, faite en sorte que le type de retour soit Integer (boxing) dans
le cas où cela marche et String dans le cas ou cela ne marche pas.
Pur le second test, faite en sorte que le type de retour soit Object (sous-typage) dans
le cas où cela marche et Double dans le cas ou cela ne marche pas.
-
Ecrire un test insertAndInvokeStaticTest (contenant encore un cas positif et un cas négatif)
qui crée un MethodHandle appelant Integer.parseInt()
toutjours avec la valeur "123" en utilisant la méthode
MethodHandles.insertArguments() ?
Faire de même avec un test bindToAndInvokeVirtualTest qui au lieu d'utiliser la méthode
MethodHandles.insertArgument() utilise la méthode
MethodHandle.bindTo()
-
Comment utiliser MethodHandles.dropArguments ?
Ecrire deux nouveau tests dropAndInvokeStaticTest et dropAndInvokeVirtualTest
montrant comment marche la méthode
MethodHandles.dropArguments
Note: si vous vous demandez à quoi sert dropArguments dans la vrai vie, la réponse est dans la suite de l'exercice.
-
On cherche maintenant à écrire un test asTypeAndInvokeExactStaticTest qui fonctionne
comme invokeStaticTest mais utilise un invokeExact à la place d'un invoke.
Pour cela, il faut utiliser la méthode
MethodHandle.asType()
pour convertir le MethodHandle qui renvoie un int vers un MethodHandle
qui renvoie un Integer explicitement.
-
Ecrire un test invokeExactConstantTest qui créer un
MethodHandle qui renvoie toujours la constant 42 typé comme un int
en utilisant la méthode
MethodHandles.constant() ?
-
Dans la classe de test, écrire une méthode privée match qui prend en paramètre
une String et renvoie un MethodHandle qui lorsqu'il est appelé avec une chaine de caractère
renvoie 1 si la chaine de caractère est égal à celle prise en paramètre de match
et -1 sinon.
Vous allez pour cela utiliser la méthode MethodHandles.guardWithTest
Ecrire un nouveau test nommé matchTest testant que la méthode match fonctionne correctement.
Note: attention target et fallback doivent avoir la même signature,
donc il faut ajuster la signature des MethodHandles avec les méthodes que l'on a vu précédemmment.
-
Enfin, toujours dans la classe de test, écrire une méthode matchAll qui prend en paramètre
une liste de String et renvoie un MethodHandle qui lorsqu'il est appelé avec une chaine
de caractère, renvoie la position dans la liste de la chaine de caractère si celle-ci appartient
à la liste ou -1 si la chaine de caractère n'appartient pas la liste.
Ecire une méthode de test matchAllTest qui test avec un assertAll et une liste
contenant cinq chaines de caractères que la méthode matchAll fonctionne correctement.
© Université de Marne-la-Vallée