Guide des utilitaires de réflexion de Goyave

Guide des utilitaires de réflexion de Goyave

1. Vue d'ensemble

Dans cet article, nous allons examiner l'APIGuavareflection, qui est nettement plus polyvalente que l'API de réflexion Java standard.

Nous utiliseronsGuava pour capturer les types génériques au moment de l'exécution, et nous ferons également bon usage deInvokable.

2. Capture de type générique à l'exécution

In Java, generics are implemented with type erasure. Cela signifie que les informations de type générique ne sont disponibles qu'au moment de la compilation et, au moment de l'exécution, elles ne sont plus disponibles.

Par exemple,List<String>, les informations sur le type générique obtiennenterased at runtime. De ce fait, il n'est pas sûr de transmettre des objetsClass génériques à l'exécution.

Nous pourrions éventuellement assigner deux listes ayant des types génériques différents à la même référence, ce qui n’est clairement pas une bonne idée:

List stringList = Lists.newArrayList();
List intList = Lists.newArrayList();

boolean result = stringList.getClass()
  .isAssignableFrom(intList.getClass());

assertTrue(result);

En raison de l'effacement de type, la méthodeisAssignableFrom() ne peut pas connaître le type générique réel des listes. Il compare essentiellement deux types qui ne sont que desList sans aucune information sur le type réel.

En utilisant l'API de réflexion Java standard, nous pouvons détecter les types génériques de méthodes et de classes. Si nous avons une méthode qui renvoie unList<String>, nous pouvons utiliser la réflexion pour obtenir le type de retour de cette méthode - unParameterizedType représentantList<String>.

La classeTypeToken utilise cette solution de contournement pour permettre la manipulation de types génériques. Nous pouvons utiliser la classeTypeToken pour capturer un type réel de liste générique et vérifier si elles peuvent vraiment être référencées par la même référence:

TypeToken> stringListToken
  = new TypeToken>() {};
TypeToken> integerListToken
  = new TypeToken>() {};
TypeToken> numberTypeToken
  = new TypeToken>() {};

assertFalse(stringListToken.isSubtypeOf(integerListToken));
assertFalse(numberTypeToken.isSubtypeOf(integerListToken));
assertTrue(integerListToken.isSubtypeOf(numberTypeToken));

Seuls lesintegerListToken peuvent être affectés à une référence de typenubmerTypeToken car une classeInteger étend une classeNumber.

3. Capture de types complexes à l'aide deTypeToken

Disons que nous voulons créer une classe paramétrée générique et que nous voulons avoir des informations sur un type générique au moment de l'exécution. Nous pouvons créer une classe qui a unTypeToken comme champ pour capturer ces informations:

abstract class ParametrizedClass {
    TypeToken type = new TypeToken(getClass()) {};
}

Ensuite, lors de la création d'une instance de cette classe, le type générique sera disponible au moment de l'exécution:

ParametrizedClass parametrizedClass = new ParametrizedClass() {};

assertEquals(parametrizedClass.type, TypeToken.of(String.class));

Nous pouvons également créer unTypeToken d'un type complexe qui a plus d'un type générique, et récupérer des informations sur chacun de ces types au moment de l'exécution:

TypeToken> funToken
  = new TypeToken>() {};

TypeToken funResultToken = funToken
  .resolveType(Function.class.getTypeParameters()[1]);

assertEquals(funResultToken, TypeToken.of(String.class));

On obtient un type de retour réel pourFunction, c'est-à-dire unString. Nous pouvons même obtenir un type de l'entrée dans la carte:

TypeToken> mapToken
  = new TypeToken>() {};

TypeToken entrySetToken = mapToken
  .resolveType(Map.class.getMethod("entrySet")
  .getGenericReturnType());

assertEquals(
  entrySetToken,
  new TypeToken>>() {});

Ici, nous utilisons une méthode de réflexiongetMethod() de la bibliothèque standard Java pour capturer le type de retour d'une méthode.

4. Invokable

LeInvokable est un wrapper fluide dejava.lang.reflect.Method etjava.lang.reflect.Constructor. Il fournit une API plus simple en plus d'une API Javareflection standard. Disons que nous avons une classe qui a deux méthodes publiques et l’une d’elles est définitive:

class CustomClass {
    public void somePublicMethod() {}

    public final void notOverridablePublicMethod() {}
}

Examinons maintenant lessomePublicMethod() à l'aide de l'API Guava et de l'API standard Javareflection:

Method method = CustomClass.class.getMethod("somePublicMethod");
Invokable invokable
  = new TypeToken() {}
  .method(method);

boolean isPublicStandradJava = Modifier.isPublic(method.getModifiers());
boolean isPublicGuava = invokable.isPublic();

assertTrue(isPublicStandradJava);
assertTrue(isPublicGuava);

Il n'y a pas beaucoup de différence entre ces deux variantes, mais vérifier si une méthode est redéfinissable est une tâche vraiment non triviale en Java. Heureusement, la méthodeisOverridable() de la classeInvokable facilite les choses:

Method method = CustomClass.class.getMethod("notOverridablePublicMethod");
Invokable invokable
 = new TypeToken() {}.method(method);

boolean isOverridableStandardJava = (!(Modifier.isFinal(method.getModifiers())
  || Modifier.isPrivate(method.getModifiers())
  || Modifier.isStatic(method.getModifiers())
  || Modifier.isFinal(method.getDeclaringClass().getModifiers())));
boolean isOverridableFinalGauava = invokable.isOverridable();

assertFalse(isOverridableStandardJava);
assertFalse(isOverridableFinalGauava);

Nous voyons que même une opération aussi simple nécessite de nombreuses vérifications à l'aide de l'API standardreflection. La classeInvokable cache cela derrière l'API qui est simple à utiliser et très concise.

5. Conclusion

Dans cet article, nous examinions l'API de réflexion Guava et la comparions à Java standard. Nous avons vu comment capturer des types génériques lors de l'exécution et comment la classeInvokable fournit une API élégante et facile à utiliser pour le code qui utilise la réflexion.

L'implémentation de tous ces exemples et extraits de code peut être trouvée dans leGitHub project - il s'agit d'un projet Maven, il devrait donc être facile à importer et à exécuter tel quel.