web-dev-qa-db-fra.com

Comment déterminer la classe d'un type générique?

Je crée une classe générique et dans l'une des méthodes j'ai besoin de connaître la classe du type générique actuellement utilisé. La raison en est que l'un des appels I de la méthode attend cela comme argument.

Exemple:

public class MyGenericClass<T> {
  public void doSomething() {
    // Snip...
    // Call to a 3rd party lib
    T bean = (T)someObject.create(T.class);
    // Snip...
  }
}

De toute évidence, l'exemple ci-dessus ne fonctionne pas et entraîne l'erreur suivante: Littéral de classe non autorisé pour le paramètre de type T.

Ma question est: quelqu'un connaît-il une bonne alternative ou solution de contournement pour cela?

59
Jaap Coomans

Toujours les mêmes problèmes: les informations génériques sont effacées lors de l'exécution, elles ne peuvent pas être récupérées. Une solution consiste à passer la classe T en paramètre d'une méthode statique:

public class MyGenericClass<T> {

    private final Class<T> clazz;

    public static <U> MyGenericClass<U> createMyGeneric(Class<U> clazz) {
        return new MyGenericClass<U>(clazz);
    }

    protected MyGenericClass(Class<T> clazz) {
        this.clazz = clazz;
    }

    public void doSomething() {
        T instance = clazz.newInstance();
    }
}

C'est moche, mais ça marche.

47
Nicolas

Je viens de signaler cette solution:

import Java.lang.reflect.ParameterizedType;

public abstract class A<B> {
    public Class<B> g() throws Exception {
        ParameterizedType superclass =
            (ParameterizedType) getClass().getGenericSuperclass();

        return (Class<B>) superclass.getActualTypeArguments()[0];
    }
}

Cela fonctionne si A se voit attribuer un type concret par une sous-classe:

new A<String>() {}.g() // this will work

class B extends A<String> {}
new B().g() // this will work

class C<T> extends A<T> {}
new C<String>().g() // this will NOT work
22
Christoph

Malheureusement, la solution de Christoph telle qu'elle est écrite ne fonctionne que dans des circonstances très limitées. [EDIT: comme indiqué ci-dessous, je ne me souviens plus de mon raisonnement pour cette phrase et il est probablement faux: "Notez que cela ne fonctionnera que dans les classes abstraites, tout d'abord."] La prochaine difficulté est que g() ne fonctionne qu'à partir des sous-classes DIRECT de A. Nous pouvons résoudre ce problème, cependant:

private Class<?> extractClassFromType(Type t) throws ClassCastException {
    if (t instanceof Class<?>) {
        return (Class<?>)t;
    }
    return (Class<?>)((ParameterizedType)t).getRawType();
}

public Class<B> g() throws ClassCastException {
    Class<?> superClass = getClass(); // initial value
    Type superType;
    do {
        superType = superClass.getGenericSuperclass();
        superClass = extractClassFromType(superType);
    } while (! (superClass.equals(A.class)));

    Type actualArg = ((ParameterizedType)superType).getActualTypeArguments()[0];
    return (Class<B>)extractClassFromType(actualArg);
}

Cela fonctionnera dans de nombreuses situations dans la pratique, mais pas tout le temps. Considérer:

public class Foo<U,T extends Collection<?>> extends A<T> {}

(new Foo<String,List<Object>>() {}).g();

Cela lancera un ClassCastException, car l'argument type ici n'est pas du tout un Class ou un ParameterizedType; c'est le TypeVariableT. Alors maintenant, vous seriez coincé à essayer de comprendre quel type T était censé représenter, et ainsi de suite dans le terrier du lapin.

Je pense que la seule réponse raisonnable et générale s'apparente à la réponse initiale de Nicolas - en général, si votre classe a besoin d'instancier des objets d'une autre classe inconnue au moment de la compilation, les utilisateurs de votre classe doivent passer cette classe littéralement ( ou, peut-être, une usine) à votre classe de manière explicite et ne pas compter uniquement sur des génériques.

4
Steven Collins

je trouve un autre moyen d'obtenir la classe de l'objet générique

public Class<?> getGenericClass(){
         Class<?> result =null;
         Type type =this.getClass().getGenericSuperclass();

         if(type instanceofParameterizedType){
              ParameterizedType pt =(ParameterizedType) type;
              Type[] fieldArgTypes = pt.getActualTypeArguments();
              result =(Class<?>) fieldArgTypes[0];
        }
        return result;
  }
2
Jet Geng

T peut être résolu assez facilement en utilisant TypeTools :

Class<T> t = (Class<T>) TypeResolver.resolveRawArguments(
                                MyGenericClass.class, getClass());
0
Jonathan

Je développerai la solution de Christoph.

Voici la classe abstraite ClassGetter:

private abstract class ClassGetter<T> {
    public final Class<T> get() {
        final ParameterizedType superclass = (ParameterizedType)
            getClass().getGenericSuperclass();
        return (Class<T>)superclass.getActualTypeArguments()[0];
    }
}

Voici une méthode statique qui utilise la classe ci-dessus pour trouver un type de classe générique:

public static <T> Class<T> getGenericClass() {
    return new ClassGetter<T>() {}.get();
}

À titre d'exemple de son utilisation, vous pouvez créer cette méthode:

public static final <T> T instantiate() {
    final Class<T> clazz = getGenericClass();
    try {
        return clazz.getConstructor((Class[])null).newInstance(null);
    } catch (Exception e) {
        return null;
    }
}

Et puis utilisez-le comme ceci:

T var = instantiate();
0
intrepidis