Qu'est-ce que les descripteurs de méthode en Java

1. Introduction


Dans ce didacticiel, nous couvrirons une API importante introduite dans Java 7 et étendue dans de nouvelles versions, java.lang.invoke.MethodHandles .



Nous apprendrons ce que sont les descripteurs de méthode, comment les créer et les utiliser.


2. Que sont les poignées de méthode?


Dans la documentation de l'API, le descripteur de méthode a cette définition:


Le descripteur de méthode est une référence exécutable typée à la méthode de base, au constructeur, au champ ou à toute autre opération de bas niveau avec des transformations supplémentaires d'arguments ou de valeurs de retour.

En d'autres termes, les descripteurs de méthode sont un mécanisme de bas niveau pour rechercher, adapter et appeler des méthodes. Les descripteurs de méthode sont immuables et n'ont aucun état d'affichage.


Pour créer et utiliser MethodHandle vous devez effectuer 4 étapes:


  1. Créer un descripteur de recherche - recherche
  2. Déclarer le type de méthode
  3. Poignée de méthode de recherche
  4. Handle de méthode d'appel

2.1. Poignées de méthode vs réflexion


Les poignées de méthode ont été introduites pour fonctionner avec l' API java.lang.reflect , comme Ils sont créés à des fins différentes et diffèrent par leurs caractéristiques.


En termes de performances, l' API MethodHandles peut être beaucoup plus rapide que l'API Reflection, car les vérifications d'accès sont effectuées au moment de la création plutôt que de l'exécution . S'il y a un responsable de la sécurité, cette différence augmente, car la recherche de classes et l'obtention de leurs éléments sont soumises à des vérifications supplémentaires.


Cependant, les performances ne sont pas le seul indicateur de l'optimalité d'une tâche, il convient de garder à l'esprit que l'API MethodHandles est plus difficile à utiliser en raison d'un manque de mécanismes tels que l'obtention de méthodes de classe, la vérification des jetons d'accès, etc.


Malgré cela, l'API MethodHandles permet les méthodes de curry, en changeant le type et l'ordre des paramètres.


Maintenant, connaissant la définition et le but de l'API MethodHandles, nous pouvons travailler avec eux. Commençons par chercher des méthodes.


3. Création d'une recherche


La première chose à faire lorsque nous voulons créer un descripteur de méthode est d'obtenir une recherche, l'objet d'usine responsable de la création de descripteurs de méthode pour les méthodes, les constructeurs et les champs visibles par la classe de recherche.


À l'aide de l'API MethodHandles, vous pouvez créer un objet de recherche avec différents modes d'accès.


Créez une recherche qui donne accès aux méthodes publiques:


 MethodHandles.Lookup publicLookup = MethodHandles.publicLookup(); 

Cependant, si nous avons besoin d'accéder aux méthodes privées et protégées, nous pouvons utiliser la méthode lookup () à la place:


 MethodHandles.Lookup lookup = MethodHandles.lookup(); 

4. Création d'un MethodType


Pour créer un MethodHandle, l'objet de recherche doit être défini sur un type, ce qui peut être fait à l'aide de la classe MethodType.


En particulier, un MethodType représente les arguments et le type de retour acceptés et renvoyés par le handle de méthode, ou transmis et attendus par le code appelant.


La structure MethodType est simple, elle est formée par le type de retour, ainsi que le nombre correspondant de types de paramètres, qui doivent être entièrement en corrélation entre le descripteur de méthode et le code appelant.


Comme MethodHandle, toutes les instances de MethodType sont immuables.


Voyons comment définir un MethodType qui définit la classe java.util.List comme type de retour et le tableau Object comme type d'entrée de données:


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

Dans le cas où la méthode renvoie un type de valeur simple ou vide, nous utilisons une classe représentant ces types (void.class, int.class …) .


Définissez un MethodType qui renvoie un int et accepte un Object:


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

Vous pouvez commencer à créer le MethodHandle.


5. Méthode de recherche


Après avoir défini le type de la méthode, pour créer un MethodHandle, vous devez le trouver à l'aide de l'objet de recherche ou publicLookup, qui renvoie également la classe source et le nom de la méthode.


La recherche fournit un ensemble de méthodes qui vous permet de trouver le descripteur de méthode de manière optimale, en tenant compte de la portée de la méthode. Considérez les approches de base, en commençant par la plus simple.


5.1. Poignée de méthode pour les méthodes


À l'aide de la méthode findVirtual() , vous pouvez créer un MethodHandle pour une méthode d'instance. Créez-le en fonction de la méthode concat() de la classe String :


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

5.2. Handle de méthode pour les méthodes statiques


Pour accéder à la méthode statique, vous pouvez utiliser la méthode findStatic() :


 MethodType mt = MethodType.methodType(List.class, Object[].class); MethodHandle asListMH = publicLookup.findStatic(Arrays.class, "asList", mt); 

Dans ce cas, nous avons créé un descripteur de méthode pour une méthode qui convertit un tableau de type Object en une List .


5.3. Poignée de méthode pour les constructeurs


Vous pouvez accéder au constructeur à l'aide de la méthode findConstructor() .


Créez un descripteur de méthode avec un comportement similaire au constructeur de la classe Integer avec le paramètre String:


 MethodType mt = MethodType.methodType(void.class, String.class); MethodHandle newIntegerMH = publicLookup.findConstructor(Integer.class, mt); 

5.4. Handle de méthode pour les champs


À l'aide du descripteur de méthode, vous pouvez également accéder aux champs.


Commençons par définir la classe Book:


 public class Book { String id; String title; // constructor } 

Comme condition initiale, nous avons une visibilité directe entre le handle de méthode et la propriété déclarée, nous pouvons donc créer un handle de méthode avec un comportement similaire à celui d'une méthode get:


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

Pour plus d'informations sur la gestion des variables / champs, consultez l'article Java 9 Variable Handles Demystified , où nous parlons de l' API java.lang.invoke.VarHandle introduite dans Java 9.


5.5. Handle de méthode pour les méthodes privées


Vous pouvez créer un descripteur de méthode pour une méthode de type private à l'aide de l' API java.lang.reflect .
Commençons par créer une méthode privée pour la classe Book:


 private String formatBook() { return id + " > " + title; } 

Nous pouvons maintenant créer un formatBook() méthode avec le comportement de la méthode formatBook() :


 Method formatBookMethod = Book.class.getDeclaredMethod("formatBook"); formatBookMethod.setAccessible(true); MethodHandle formatBookMH = lookup.unreflect(formatBookMethod); 

6. Poignée de méthode d'appel


Une fois que nous avons créé notre descripteur de méthode, passez à l'étape suivante. La classe MethodHandle nous donne 3 façons différentes d'appeler le handle de méthode: invoke() , invokeWithArugments() et invokeExact() .


Commençons par la méthode invoke .


6.1. Call Method Handle


Lorsque vous utilisez la méthode invoke() , le nombre d'arguments (arity) est fixe, mais il est possible d'effectuer un transtypage de type et un compactage / décompactage des arguments et des types de la valeur de retour.


Voyons maintenant comment invoke() peut être utilisé avec un argument compressé:


 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); 

Dans ce cas, replaceMH nécessite des arguments char , mais la méthode invoke() décompresse l'argument Character avant de s'exécuter.


6.2. Appel avec arguments


L'invocation du invokeWithArguments méthode à l'aide d' invokeWithArguments a la moindre restriction.


En fait, en plus de vérifier les types et les arguments de conditionnement / décompression et les valeurs de retour, il vous permet de faire des appels avec un nombre variable de paramètres.


En pratique, nous pouvons créer une liste entière avec un tableau de valeurs int de longueur inconnue:


 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)); 

6.3. Appelez exactement


Si nous avons besoin que le handle de méthode soit exécuté de manière plus restrictive (par l'ensemble d'arguments et leur type), nous utilisons la méthode invokeExact() .


En fait, il ne permet pas de transtyper les types d'une classe et nécessite un ensemble fixe d'arguments.


Voyons comment ajouter deux valeurs int à l'aide du descripteur de méthode:


 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); 

Dans ce cas, si nous transmettons un nombre qui n'est pas un invokeExact méthode invokeExact , lorsque nous appelons, nous obtenons une WrongMethodTypeException .


7. Travailler avec des tableaux


MethodHandles peut fonctionner non seulement avec des champs et des objets, mais aussi avec des tableaux. À l'aide de l'API asSpreader() , vous pouvez créer un asSpreader() méthode qui prend en charge les tableaux comme arguments positionnels.


Dans ce cas, le descripteur de méthode prend un tableau, distribuant ses éléments sous forme d'arguments positionnels et, éventuellement, la longueur du tableau.


Voyons comment obtenir le handle de méthode pour vérifier si les arguments du tableau sont la même chaîne:


 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" })); 

8. Clarification de la poignée de méthode


Une fois le descripteur de méthode défini, vous pouvez l'affiner en le liant à l'argument, sans appeler la méthode.


Par exemple, en Java 9, cette astuce est utilisée pour optimiser la concaténation de chaînes.


Voyons comment la concaténation peut être effectuée en attachant le suffixe à concatMH :


 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!")); 

9. Mises à jour Java 9


Java 9 a apporté plusieurs modifications à l'API MethodHandles pour en faciliter l'utilisation.


Les mises à jour concernent 3 aspects principaux:


  • Fonctions de recherche - permettent de rechercher à partir de différents contextes et prennent en charge des méthodes non abstraites dans les interfaces.
  • Opérations avec des arguments - amélioration de la fonctionnalité de pliage, de collecte et de distribution des arguments.
  • Des combinaisons supplémentaires ajoutent des opérations de boucle ( loop , whileLoop , doWhileLoop , ...) et une gestion des exceptions améliorée avec tryFinally .

Ces changements ont entraîné d'autres innovations utiles:


  • Optimisation améliorée du compilateur JVM
  • Instance diminuée
  • Concrétisation de l'utilisation de l'API MethodHandles

Une liste plus détaillée des modifications est disponible dans l'API Javadoc MethodHandles .


10. Conclusion


Dans cet article, nous avons rencontré l'API MethodHandles et avons également appris ce que sont les descripteurs de méthode et comment les utiliser.


Nous avons également décrit comment il est associé à l'API Reflection. Étant donné que l'appel des descripteurs de méthode est une opération de bas niveau, leur utilisation n'est justifiée que s'ils correspondent exactement à vos tâches.


Comme d'habitude, tout le code source de l'article est disponible sur Github .

Source: https://habr.com/ru/post/fr431922/


All Articles