web-dev-qa-db-fra.com

Obtenir le type d'un paramètre générique en Java avec réflexion

Est-il possible d'obtenir le type d'un paramètre générique?

Un exemple:

public final class Voodoo {
    public static void chill(List<?> aListWithTypeSpiderMan) {
        // Here I'd like to get the Class-Object 'SpiderMan'
        Class typeOfTheList = ???;
    }

    public static void main(String... args) {
        chill(new ArrayList<SpiderMan>());
    }
}
120
cimnine

Une construction, je suis tombé sur une fois sur ressemblait

Class<T> persistentClass = (Class<T>)
   ((ParameterizedType)getClass().getGenericSuperclass())
      .getActualTypeArguments()[0];

Il semble donc y avoir un peu de magie par réflexion que je ne comprends malheureusement pas entièrement ... Désolé.

134
DerMike

Je veux essayer de décomposer la réponse de @DerMike pour expliquer:

Premièrement, l'effacement des types ne signifie pas que le JDK élimine les informations de type au moment de l'exécution. Il s'agit d'une méthode permettant la coexistence dans le même langage de la vérification de type à la compilation et de la compatibilité au moment de l'exécution. Comme ce bloc de code l'indique, le JDK conserve les informations de type effacées - elles ne sont tout simplement pas associées aux conversions vérifiées et aux commandes.

Deuxièmement, cela fournit des informations de type génériques à une classe générique à exactement un niveau dans l’ordre hiérarchique par rapport au type concret en cours de vérification - c’est-à-dire. une classe parent abstraite avec des paramètres de type génériques peut trouver les types concrets correspondant à ses paramètres de type pour une implémentation concrète d'elle-même qui directement en hérite. Si cette classe n'était pas abstraite et instanciée, ou si la mise en œuvre concrète avait deux niveaux de moins, cela ne fonctionnerait pas (même si un peu de jimmying pourrait le faire s'appliquer à un nombre prédéterminé de niveaux au-delà d'un ou jusqu'à la classe la plus basse. avec X paramètres de type génériques, et cetera).

Quoi qu'il en soit, passons à l'explication. Voici à nouveau le code, séparé en lignes pour plus de facilité:

 1 # Classe genericParameter0OfThisClass = 
 2 # (Classe) 
 3 # ((ParameterizedType) 
 4 # getClass () 
 5 # .getGenericSuperclass ()) 
 6 # .getActualTypeArguments () [0]; 

Soit 'nous' la classe abstraite avec les types génériques contenant ce code. En lisant cela à peu près de l'intérieur:

  • La ligne 4 récupère l'instance de classe de la classe concrète actuelle. Ceci identifie le type concret de notre descendant immédiat . 
  • La ligne 5 obtient le supertype de cette classe en tant que type; c'est nous. Puisque nous sommes un type paramétrique, nous pouvons nous lancer en toute sécurité vers ParameterizedType (ligne 3). La clé est que lorsque Java détermine cet objet Type, il utilise les informations de type présentes dans l'enfant pour associer les informations de type à nos paramètres de type dans la nouvelle instance ParameterizedType. Nous pouvons maintenant accéder à des types concrets pour nos génériques . 
  • La ligne 6 récupère le tableau des types mappés dans nos génériques, dans l’ordre indiqué dans le code de classe. Pour cet exemple, nous extrayons le premier paramètre. Ceci revient en tant que Type . 
  • La ligne 2 lance le type final retourné à une classe. Ceci est sûr car nous savons quels types nos paramètres de types génériques sont capables de prendre et pouvons confirmer qu'ils seront tous des classes (je ne sais pas comment en Java on obtiendrait un paramètre générique qui ne possède pas d'instance de classe associé, en fait) .

...Et c'est à peu près tout. Nous poussons donc en nous les informations de type issues de notre propre implémentation concrète et les utilisons pour accéder à un handle de classe. nous pourrions doubler getGenericSuperclass () et accéder à deux niveaux, ou éliminer getGenericSuperclass () et obtenir des valeurs pour nous-mêmes comme type concret (avertissement: je n'ai pas testé ces scénarios, ils ne m'ont pas encore été proposés).

Cela devient délicat si vos enfants concrets ont un nombre arbitraire de Houblons, ou si vous êtes concret et non final, et particulièrement délicat si vous vous attendez à ce que l'un de vos enfants (plus ou moins profond) ait ses propres génériques. Mais vous pouvez généralement concevoir en tenant compte de ces considérations, ce qui vous permet de faire presque tout le chemin.

J'espère que cela a aidé quelqu'un! Je reconnais que ce post est ancien. Je vais probablement couper cette explication et la garder pour d'autres questions.

123
Mark McKenna

En fait, ça a fonctionné. Considérez l'extrait suivant:

Method m;
Type[] genericParameterTypes = m.getGenericParameterTypes();
for (int i = 0; i < genericParameterTypes.length; i++) {
     if( genericParameterTypes[i] instanceof ParameterizedType ) {
                Type[] parameters = ((ParameterizedType)genericParameterTypes[i]).getActualTypeArguments();
//parameters[0] contains Java.lang.String for method like "method(List<String> value)"

     }
 }

J'utilise jdk 1.6

20
Andrey Rodionov

Il y a une solution en fait, en appliquant l'astuce "classe anonyme" } et les idées du jetons de type super :

public final class Voodoo {
    public static void chill(final List<?> aListWithSomeType) {
        // Here I'd like to get the Class-Object 'SpiderMan'
        System.out.println(aListWithSomeType.getClass().getGenericSuperclass());
        System.out.println(((ParameterizedType) aListWithSomeType
            .getClass()
            .getGenericSuperclass()).getActualTypeArguments()[0]);
    }
    public static void main(String... args) {
        chill(new ArrayList<SpiderMan>() {});
    }
}
class SpiderMan {
}

L'astuce consiste à créer une classe anonymous, new ArrayList<SpiderMan>() {}, à la place de la (simple) new ArrayList<SpiderMan>() d'origine. L'utilisation d'une classe anormale (si possible) garantit que le compilateur conserve des informations sur l'argument de type SpiderMan donné au paramètre de type List<?>. Voilà!

15

Non, ce n'est pas possible. En raison de problèmes de compatibilité descendante, les génériques de Java sont basés sur type erasure , i.a. au moment de l'exécution, tout ce que vous avez est un objet List non générique. Il existe des informations sur les paramètres de type au moment de l'exécution, mais elles se trouvent dans les définitions de classe (par exemple, vous pouvez demander " quel type générique la définition de ce champ utilise-t-elle? "), Pas dans les instances d'objet.

6
Michael Borgwardt

En raison de l'effacement du type, le seul moyen de connaître le type de la liste serait de le transmettre en tant que paramètre à la méthode:

public class Main {

    public static void main(String[] args) {
        doStuff(new LinkedList<String>(), String.class);

    }

    public static <E> void doStuff(List<E> list, Class<E> clazz) {

    }

}
6
Jared Russell

Comme l'a souligné @bertolami, il est impossible d'utiliser un type de variable et d'obtenir sa valeur future (le contenu de la variable typeOfList).

Néanmoins, vous pouvez passer la classe en tant que paramètre comme ceci:

public final class voodoo {
    public static void chill(List<T> aListWithTypeSpiderMan, Class<T> clazz) {
        // Here I'd like to get the Class-Object 'SpiderMan'
        Class typeOfTheList = clazz;
    }

    public static void main(String... args) {
        chill(new List<SpiderMan>(), Spiderman.class );
    }
}

C’est plus ou moins ce que fait Google lorsque vous devez transmettre une variable de classe au constructeur de ActivityInstrumentationTestCase2 .

5
Snicolas

Annexe à la réponse de @ DerMike pour obtenir le paramètre générique d'une interface paramétrée (en utilisant la méthode #getGenericInterfaces () dans une méthode par défaut de Java-8 pour éviter les doublons):

import Java.lang.reflect.ParameterizedType; 

public class ParametrizedStuff {

@SuppressWarnings("unchecked")
interface Awesomable<T> {
    default Class<T> parameterizedType() {
        return (Class<T>) ((ParameterizedType)
        this.getClass().getGenericInterfaces()[0])
            .getActualTypeArguments()[0];
    }
}

static class Beer {};
static class EstrellaGalicia implements Awesomable<Beer> {};

public static void main(String[] args) {
    System.out.println("Type is: " + new EstrellaGalicia().parameterizedType());
    // --> Type is: ParameterizedStuff$Beer
}
5
Campa

Non ce n'est pas possible.

Vous pouvez obtenir un type générique d'un champ étant donné qu'une classe est la seule exception à cette règle et même un peu un bidouillage.

Voir Connaître le type du générique en Java pour un exemple.

3
cletus

La réponse rapide à la Question est non, vous ne pouvez pas, en raison de l'effacement du type générique Java.

La réponse plus longue serait que si vous avez créé votre liste comme ceci:

new ArrayList<SpideMan>(){}

Ensuite, dans ce cas, le type générique est conservé dans la superclasse générique de la nouvelle classe anonyme ci-dessus. 

Ce n'est pas que je recommande de faire cela avec les listes, mais c'est une implémentation d'écouteur:

new Listener<Type>() { public void doSomething(Type t){...}}

Et comme l'extrapolation des types génériques de super classes et d'interfaces change entre les machines virtuelles, la solution générique n'est pas aussi simple que certaines réponses pourraient l'indiquer.

Voici maintenant je l'ai fait.

0
TacB0sS

J'ai codé ceci pour des méthodes qui s'attendent à accepter ou à renvoyer Iterable<?...>. Voici le code:

/**
 * Assuming the given method returns or takes an Iterable<T>, this determines the type T.
 * T may or may not extend WindupVertexFrame.
 */
private static Class typeOfIterable(Method method, boolean setter)
{
    Type type;
    if (setter) {
        Type[] types = method.getGenericParameterTypes();
        // The first parameter to the method expected to be Iterable<...> .
        if (types.length == 0)
            throw new IllegalArgumentException("Given method has 0 params: " + method);
        type = types[0];
    }
    else {
        type = method.getGenericReturnType();
    }

    // Now get the parametrized type of the generic.
    if (!(type instanceof ParameterizedType))
        throw new IllegalArgumentException("Given method's 1st param type is not parametrized generic: " + method);
    ParameterizedType pType = (ParameterizedType) type;
    final Type[] actualArgs = pType.getActualTypeArguments();
    if (actualArgs.length == 0)
        throw new IllegalArgumentException("Given method's 1st param type is not parametrized generic: " + method);

    Type t = actualArgs[0];
    if (t instanceof Class)
        return (Class<?>) t;

    if (t instanceof TypeVariable){
        TypeVariable tv =  (TypeVariable) actualArgs[0];
        AnnotatedType[] annotatedBounds = tv.getAnnotatedBounds();///
        GenericDeclaration genericDeclaration = tv.getGenericDeclaration();///
        return (Class) tv.getAnnotatedBounds()[0].getType();
    }

    throw new IllegalArgumentException("Unknown kind of type: " + t.getTypeName());
}
0
Ondra Žižka

Ceci est impossible car les génériques en Java ne sont pris en compte qu'au moment de la compilation. Ainsi, les génériques Java ne sont qu’une sorte de pré-processeur. Cependant, vous pouvez obtenir la classe réelle des membres de la liste.

0
bertolami

Vous ne pouvez pas obtenir un paramètre générique d'une variable. Mais vous pouvez à partir d'une déclaration de méthode ou de champ:

Method method = getClass().getDeclaredMethod("chill", List.class);
Type[] params = method.getGenericParameterTypes();
ParameterizedType firstParam = (ParameterizedType) params[0];
Type[] paramsOfFirstGeneric = firstParam.getActualTypeArguments();
0
Anton Pogonets

Vous pouvez obtenir le type d'un paramètre générique avec réflexion comme dans cet exemple que j'ai trouvé ici :

import Java.lang.reflect.ParameterizedType;
import Java.lang.reflect.Type;

public class Home<E> {
    @SuppressWarnings ("unchecked")
    public Class<E> getTypeParameterClass(){
        Type type = getClass().getGenericSuperclass();
        ParameterizedType paramType = (ParameterizedType) type;
        return (Class<E>) paramType.getActualTypeArguments()[0];
    }

    private static class StringHome extends Home<String>{}
    private static class StringBuilderHome extends Home<StringBuilder>{}
    private static class StringBufferHome extends Home<StringBuffer>{}   

    /**
     * This prints "String", "StringBuilder" and "StringBuffer"
     */
    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        Object object0 = new StringHome().getTypeParameterClass().newInstance();
        Object object1 = new StringBuilderHome().getTypeParameterClass().newInstance();
        Object object2 = new StringBufferHome().getTypeParameterClass().newInstance();
        System.out.println(object0.getClass().getSimpleName());
        System.out.println(object1.getClass().getSimpleName());
        System.out.println(object2.getClass().getSimpleName());
    }
}
0
madx

Juste pour moi, lire cet extrait de code était difficile, je viens de le diviser en 2 lignes lisibles:

// assuming that the Generic Type parameter is of type "T"
ParameterizedType p = (ParameterizedType) getClass().getGenericSuperclass();
Class<T> c =(Class<T>)p.getActualTypeArguments()[0];

Je voulais créer une instance du paramètre Type sans avoir aucun paramètre à ma méthode:

publc T getNewTypeInstance(){
    ParameterizedType p = (ParameterizedType) getClass().getGenericSuperclass();
    Class<T> c =(Class<T>)p.getActualTypeArguments()[0];

    // for me i wanted to get the type to create an instance
    // from the no-args default constructor
    T t = null;
    try{
        t = c.newInstance();
    }catch(Exception e){
        // no default constructor available
    }
    return t;
}
0
Ahmed Adel Ismail