2017-07-09 5 views
2

J'ai des méthodes avec un seul paramètre de type Event<Something> et besoin d'extraire le Something ou son type brut. Il peut ressembler àComment obtenir l'argument du type de paramètre de la méthode formelle?

void method1(Event<MyEnum1> event) {} 
<E extends Enum<E>> method2(Event<E> event) {} 
<K, V> method3(Event<Map<K, V>> event) {} 

et je voudrais un mappage de fonction

method1 -> MyEnum.class 
method2 -> Enum.class 
method3 -> Map.class 

Pour method1, il utilise (ParameterizedType) method.getGenericParameterTypes() facile. Pour method2, je peux utiliser Goyave

TypeToken.of(method.getDeclaringClass()) 
    .method(method) 
    .getTypeParameters()[0] 
    .getBounds() 

pour obtenir un Type et

TypeToken.of(method.getDeclaringClass()) 
    .method(method) 
    .getParameters() 
    .get(0) 

pour obtenir le Parameter. Je suppose, je dois résoudre le Parameter en utilisant le Type, mais je suis coincé ici.

Je me fous de la method3, mais il serait bon de savoir d'une manière générale. Je ne me soucie pas du type de paramètres réels (je suis conscient de l'effacement).

+0

Il peut être intéressant pour quelqu'un de savoir pourquoi j'échoué. Comme le montre la réponse acceptée, c'est en fait simple. Mon problème était que j'ai découvert que 'typeArgument' est' TypeVariableImpl', l'a ouvert dans Eclipse et vu qu'il est privé et n'implémente aucune interface (ce qui est impossible car c'est un 'Type', mais je l'ai manqué). La classe correspondante était 'sun.reflect.generics.reflectiveObjects.TypeVariableImpl' (manquante dans sources.jar) et non celle que je regardais. – maaartinus

Répondre

3

Vous n'avez pas besoin de TypeToken pour cela. java.lang.reflect fournit tout ce dont vous avez besoin.

Vous devrez prendre en compte plusieurs cas. Je vais supposer que le premier paramètre de toute méthode fournie est toujours de type Event (ou vraiment un type paramétré). Vous devrez gérer les 5 sous-types de Type. À partir de vos exemples, vous devrez vérifier si l'argument type est un type concret (MyEnum), une variable de type (E délimitée par Enum) ou un type paramétré (Map<K,V>).

De plus, il existe WildcardType que vous pouvez gérer comme la variable de type et extraire les limites. Enfin, vous devez traiter les types de tableaux génériques avec GenericArrayType (Event<T[]> ou Event<List<String>[]>). Je les ai laissés de côté, mais il ne fait que réappliquer les mêmes règles pour les autres types.

Je suppose, pour la variable de type type, que la borne peut être une autre variable de type, nous devons donc réapparaître jusqu'à ce que nous trouvions la borne concrète.

// get raw type argument of first parameter 
public static Class<?> getRawTypeArgument(Method method) { 
    Parameter parameter = method.getParameters()[0]; 
    Type type = parameter.getParameterizedType(); 
    /// assume it's parameterized 
    ParameterizedType parameterizedType = (ParameterizedType) type; 
    // assume there's one type argument 
    Type typeArgument = parameterizedType.getActualTypeArguments()[0]; 
    if (typeArgument instanceof TypeVariable<?>) { 
     TypeVariable<?> typeVariableArgument = (TypeVariable<?>) typeArgument; 
     return recursivelyExtractBound(typeVariableArgument); 
    } else if (typeArgument instanceof Class<?>) { 
     return (Class<?>) typeArgument; 
    } else if (typeArgument instanceof ParameterizedType) { 
     ParameterizedType parameterizedTypeArgument = (ParameterizedType) typeArgument; 
     return (Class<?>) parameterizedTypeArgument.getRawType(); 
    } 
    throw new AssertionError("Consider wildcard and generic type"); 
} 

private static Class<?> recursivelyExtractBound(TypeVariable<?> typeVariable) { 
    // assume first 
    Type bound = typeVariable.getBounds()[0]; 
    if (bound instanceof Class<?>) { 
     return (Class<?>) bound; 
    } else if (bound instanceof TypeVariable<?>) { 
     TypeVariable<?> nested = (TypeVariable<?>) bound; 
     return recursivelyExtractBound(nested); 
    } else if (bound instanceof ParameterizedType) { 
     ParameterizedType parameterizedTypeArgument = (ParameterizedType) bound; 
     return (Class<?>) parameterizedTypeArgument.getRawType(); 
    } 
    throw new AssertionError("Are there others?"); 
} 

Avec un petit programme pilote

public static void main(String[] args) throws Exception { 
    Class<?> clazz = Example.class; 
    for (Method method : clazz.getDeclaredMethods()) { 
     if (!method.getName().startsWith("method")) { 
      continue; 
     } 
     System.out.println("Method '" + method + "' -> " + getRawTypeArgument(method)); 
    } 
} 

les impressions ci-dessus sur

Method 'java.lang.Object com.example.Example.method3(com.example.root.Event)' -> interface java.util.Map 
Method 'java.lang.Object com.example.Example.method2(com.example.root.Event)' -> class java.lang.Enum 
Method 'void com.example.Example.method1(com.example.Event)' -> class com.example.MyEnum1 
+1

C'est vraiment génial! Cependant, je changerais simplement 'IllegalStateException' en' AssertionError'. Parce que c'est vous en tant que programmeur qui affirme qu'il n'y a pas d'autres cas couverts. En outre, le ['IllegalStateException'] (http://docs.oracle.com/javase/8/docs/api/java/lang/IllegalStateException.html) fait principalement référence à l'état de l'objet appelé, et ici les méthodes appelées sont statiques. Statique et l'état ne vont généralement pas bien ensemble;) –

+0

@ OlivierGrégoire Ouais. –

+0

@ OlivierGrégoire Je ne suis pas sûr de ce changement. Je préférerais une 'AssertionRuntimeException' si elle existait. Une 'AssertionError' pourrait faire glisser des prises et causer des problèmes ... ou peut-être pas; les goyaves l'utilisent aussi. – maaartinus