web-dev-qa-db-fra.com

Comment obtenir une instance de classe de type générique T

J'ai une classe de génériques, Foo<T>. Dans une méthode de Foo, je souhaite obtenir l'instance de classe de type T, mais je ne peux simplement pas appeler T.class.

Quel est le moyen préféré de le contourner en utilisant T.class?

643
robinmag

La réponse courte est qu'il n'y a aucun moyen de connaître le type d'exécution des paramètres de type générique en Java. Je suggère de lire le chapitre sur l’effacement des types dans Tutoriel Java pour plus de détails.

Une solution courante consiste à passer la Class du paramètre type dans le constructeur du type générique, par exemple.

class Foo<T> {
    final Class<T> typeParameterClass;

    public Foo(Class<T> typeParameterClass) {
        this.typeParameterClass = typeParameterClass;
    }

    public void bar() {
        // you can access the typeParameterClass here and do whatever you like
    }
}
523
Zsolt Török

Je cherchais un moyen de le faire moi-même sans ajouter de dépendance supplémentaire au classpath. Après quelques recherches, j'ai trouvé que est possible tant que vous avez un supertype générique. Cela me convenait, car je travaillais avec une couche DAO avec un sur-type de couche générique. Si cela correspond à votre scénario, alors c'est l'approche la plus soignée, à mon humble avis.

La plupart des cas d'utilisation de génériques que j'ai rencontrés ont une sorte de supertype générique, par exemple. List<T> pour ArrayList<T> ou GenericDAO<T> pour DAO<T>, etc.

Solution pure Java

L'article Accès aux types génériques lors de l'exécution en Java explique comment procéder à l'aide de Java pur.

Solution de printemps

Mon projet utilisait Spring , ce qui est encore meilleur car Spring dispose d'une méthode utilitaire pratique pour trouver le type. C’est la meilleure approche pour moi car elle a l’air plus soignée. Je suppose que si vous n’utilisiez pas Spring, vous pourriez écrire votre propre méthode d’utilité.

import org.springframework.core.GenericTypeResolver;

public abstract class AbstractHibernateDao<T extends DomainObject> implements DataAccessObject<T>
{

    @Autowired
    private SessionFactory sessionFactory;

    private final Class<T> genericType;

    private final String RECORD_COUNT_HQL;
    private final String FIND_ALL_HQL;

    @SuppressWarnings("unchecked")
    public AbstractHibernateDao()
    {
        this.genericType = (Class<T>) GenericTypeResolver.resolveTypeArgument(getClass(), AbstractHibernateDao.class);
        this.RECORD_COUNT_HQL = "select count(*) from " + this.genericType.getName();
        this.FIND_ALL_HQL = "from " + this.genericType.getName() + " t ";
    }
207
Ben Thurley

Il existe toutefois une petite lacune: si vous définissez votre classe Foo comme abstraite. Cela voudrait dire que vous devez instancier votre classe comme:

Foo<MyType> myFoo = new Foo<MyType>(){};

(Notez les doubles accolades à la fin.)

Maintenant, vous pouvez récupérer le type de T au moment de l'exécution:

Type mySuperclass = myFoo.getClass().getGenericSuperclass();
Type tType = ((ParameterizedType)mySuperclass).getActualTypeArguments()[0];

Notez cependant que mySuperclass doit être la superclasse de la définition de classe définissant le type final pour T.

Ce n’est pas très élégant non plus, mais vous devez décider si vous préférez new Foo<MyType>(){} ou new Foo<MyType>(MyType.class); dans votre code.


Par exemple:

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

import Java.util.ArrayDeque;
import Java.util.Deque;
import Java.util.NoSuchElementException;

/**
 * Captures and silently ignores stack exceptions upon popping.
 */
public abstract class SilentStack<E> extends ArrayDeque<E> {
  public E pop() {
    try {
      return super.pop();
    }
    catch( NoSuchElementException nsee ) {
      return create();
    }
  }

  public E create() {
    try {
      Type sooper = getClass().getGenericSuperclass();
      Type t = ((ParameterizedType)sooper).getActualTypeArguments()[ 0 ];

      return (E)(Class.forName( t.toString() ).newInstance());
    }
    catch( Exception e ) {
      return null;
    }
  }
}

Ensuite:

public class Main {
    // Note the braces...
    private Deque<String> stack = new SilentStack<String>(){};

    public static void main( String args[] ) {
      // Returns a new instance of String.
      String s = stack.pop();
      System.out.printf( "s = '%s'\n", s );
    }
}
90
bert bruynooghe

Une approche/solution de contournement/solution standard consiste à ajouter un objet class au (x) constructeur (s), par exemple:

 public class Foo<T> {

    private Class<T> type;
    public Foo(Class<T> type) {
      this.type = type;
    }

    public Class<T> getType() {
      return type;
    }

    public T newInstance() {
      return type.newInstance();
    }
 }
35
Andreas_D

Imaginez que vous avez une superclasse abstraite générique:

public abstract class Foo<? extends T> {}

Et puis vous avez une deuxième classe qui étend Foo avec une barre générique qui étend T:

public class Second extends Foo<Bar> {}

Vous pouvez obtenir la classe Bar.class dans la classe Foo en sélectionnant le Type (de bert bruynooghe answer) et en le déduisant à l'aide de Class instance:

Type mySuperclass = myFoo.getClass().getGenericSuperclass();
Type tType = ((ParameterizedType)mySuperclass).getActualTypeArguments()[0];
//Parse it as String
String className = tType.toString().split(" ")[1];
Class clazz = Class.forName(className);

Vous devez noter que cette opération n'est pas idéale, c'est donc une bonne idée de mettre en cache la valeur calculée pour éviter plusieurs calculs à ce sujet. L'une des utilisations typiques est l'implémentation DAO générique.

La mise en œuvre finale:

public abstract class Foo<T> {

    private Class<T> inferedClass;

    public Class<T> getGenericClass(){
        if(inferedClass == null){
            Type mySuperclass = getClass().getGenericSuperclass();
            Type tType = ((ParameterizedType)mySuperclass).getActualTypeArguments()[0];
            String className = tType.toString().split(" ")[1];
            inferedClass = Class.forName(className);
        }
        return inferedClass;
    }
}

La valeur renvoyée est Bar.class lorsqu'elle est appelée à partir de la classe Foo dans une autre fonction ou de la classe Bar.

18
droidpl

Voici une solution de travail:

@SuppressWarnings("unchecked")
private Class<T> getGenericTypeClass() {
    try {
        String className = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0].getTypeName();
        Class<?> clazz = Class.forName(className);
        return (Class<T>) clazz;
    } catch (Exception e) {
        throw new IllegalStateException("Class is not parametrized with generic type!!! Please use extends <> ");
    }
} 

NOTES: Peut être utilisé uniquement comme superclasse

  1. Doit être étendu avec une classe typée (Child extends Generic<Integer>)

OU

  1. A créer comme implémentation anonyme (new Generic<Integer>() {};)
15
Yaroslav Kovbas

Vous ne pouvez pas le faire à cause de l'effacement du type. Voir aussi Question relative au débordement de pile Génériques Java - type effacement - quand et que se passe-t-il.

10
Konrad Garus

J'ai eu ce problème dans une classe générique abstraite. Dans ce cas particulier, la solution est plus simple:

abstract class Foo<T> {
    abstract Class<T> getTClass();
    //...
}

et plus tard la classe dérivée:

class Bar extends Foo<Whatever> {
    @Override
    Class<T> getTClass() {
        return Whatever.class;
    }
}
9
user1154664

Un meilleur itinéraire que la classe suggéré par les autres consiste à transmettre un objet capable de faire ce que vous auriez fait avec la classe, par exemple, créer une nouvelle instance.

interface Factory<T> {
  T apply();
}

<T> void List<T> make10(Factory<T> factory) {
  List<T> result = new ArrayList<T>();
  for (int a = 0; a < 10; a++)
    result.add(factory.apply());
  return result;
}

class FooFactory<T> implements Factory<Foo<T>> {
  public Foo<T> apply() {
    return new Foo<T>();
  }
}

List<Foo<Integer>> foos = make10(new FooFactory<Integer>());
8
Ricky Clarkson

J'ai une solution (laide mais efficace) à ce problème, que j'ai utilisé récemment:

import Java.lang.reflect.TypeVariable;


public static <T> Class<T> getGenericClass()
{
    __<T> ins = new __<T>();
    TypeVariable<?>[] cls = ins.getClass().getTypeParameters(); 

    return (Class<T>)cls[0].getClass();
}

private final class __<T> // generic helper class which does only provide type information
{
    private __()
    {
    }
}
5
unknown6656

C'est possible:

class Foo<T> {
  Class<T> clazz = (Class<T>) DAOUtil.getTypeArguments(Foo.class, this.getClass()).get(0);
}

Vous avez besoin de deux fonctions de svn/trunk/dao/src/main/Java/com/googlecode/genericdao/dao/DAOUtil.Java .

Pour plus d'explications, voir Génériques réfléchissants.

4
Peter Tseng

J'ai trouvé un moyen générique et simple de le faire. Dans ma classe, j'ai créé une méthode qui renvoie le type générique en fonction de sa position dans la définition de la classe. Supposons une définition de classe comme celle-ci:

public class MyClass<A, B, C> {

}

Créons maintenant quelques attributs pour conserver les types:

public class MyClass<A, B, C> {

    private Class<A> aType;

    private Class<B> bType;

    private Class<C> cType;

// Getters and setters (not necessary if you are going to use them internally)

    } 

Ensuite, vous pouvez créer une méthode générique qui renvoie le type en fonction de l'index de la définition générique:

   /**
     * Returns a {@link Type} object to identify generic types
     * @return type
     */
    private Type getGenericClassType(int index) {
        // To make it use generics without supplying the class type
        Type type = getClass().getGenericSuperclass();

        while (!(type instanceof ParameterizedType)) {
            if (type instanceof ParameterizedType) {
                type = ((Class<?>) ((ParameterizedType) type).getRawType()).getGenericSuperclass();
            } else {
                type = ((Class<?>) type).getGenericSuperclass();
            }
        }

        return ((ParameterizedType) type).getActualTypeArguments()[index];
    }

Enfin, dans le constructeur, appelez simplement la méthode et envoyez l’index pour chaque type. Le code complet devrait ressembler à:

public class MyClass<A, B, C> {

    private Class<A> aType;

    private Class<B> bType;

    private Class<C> cType;


    public MyClass() {
      this.aType = (Class<A>) getGenericClassType(0);
      this.bType = (Class<B>) getGenericClassType(1);
      this.cType = (Class<C>) getGenericClassType(2);
    }

   /**
     * Returns a {@link Type} object to identify generic types
     * @return type
     */
    private Type getGenericClassType(int index) {

        Type type = getClass().getGenericSuperclass();

        while (!(type instanceof ParameterizedType)) {
            if (type instanceof ParameterizedType) {
                type = ((Class<?>) ((ParameterizedType) type).getRawType()).getGenericSuperclass();
            } else {
                type = ((Class<?>) type).getGenericSuperclass();
            }
        }

        return ((ParameterizedType) type).getActualTypeArguments()[index];
    }
}
3
Juan Urrego

Comme expliqué dans d'autres réponses, pour utiliser cette approche ParameterizedType, vous devez étendre la classe, mais cela semble être un travail supplémentaire pour créer une toute nouvelle classe qui l'étend ...

Ainsi, rendre la classe abstraite vous oblige à l'étendre, satisfaisant ainsi l'exigence de sous-classes. (en utilisant @Getter de Lombok).

@Getter
public abstract class ConfigurationDefinition<T> {

    private Class<T> type;
    ...

    public ConfigurationDefinition(...) {
        this.type = (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        ...
    }
}

Maintenant, étendez-le sans définir une nouvelle classe. (Notez le {} à la fin ... étendu, mais ne remplacez rien - sauf si vous le souhaitez).

private ConfigurationDefinition<String> myConfigA = new ConfigurationDefinition<String>(...){};
private ConfigurationDefinition<File> myConfigB = new ConfigurationDefinition<File>(...){};
...
Class stringType = myConfigA.getType();
Class fileType = myConfigB.getType();
2
Riaan Schutte
   public <T> T yourMethodSignature(Class<T> type) {

        // get some object and check the type match the given type
        Object result = ...            

        if (type.isAssignableFrom(result.getClass())) {
            return (T)result;
        } else {
            // handle the error
        }
   }
1
Abel ANEIROS

Si vous développez ou implémentez une classe/interface utilisant des génériques, vous pouvez obtenir le type générique de la classe/interface parente, sans aucune modification de la classe/interface existante.

Il pourrait y avoir trois possibilités,

Cas 1 Lorsque votre classe prolonge une classe qui utilise des génériques

public class TestGenerics {
    public static void main(String[] args) {
        Type type = TestMySuperGenericType.class.getGenericSuperclass();
        Type[] gTypes = ((ParameterizedType)type).getActualTypeArguments();
        for(Type gType : gTypes){
            System.out.println("Generic type:"+gType.toString());
        }
    }
}

class GenericClass<T> {
    public void print(T obj){};
}

class TestMySuperGenericType extends GenericClass<Integer> {
}

Cas 2 Lorsque votre classe implémente une interface utilisant Generics

public class TestGenerics {
    public static void main(String[] args) {
        Type[] interfaces = TestMySuperGenericType.class.getGenericInterfaces();
        for(Type type : interfaces){
            Type[] gTypes = ((ParameterizedType)type).getActualTypeArguments();
            for(Type gType : gTypes){
                System.out.println("Generic type:"+gType.toString());
            }
        }
    }
}

interface GenericClass<T> {
    public void print(T obj);
}

class TestMySuperGenericType implements GenericClass<Integer> {
    public void print(Integer obj){}
}

Cas Lorsque votre interface étend une interface utilisant Generics

public class TestGenerics {
    public static void main(String[] args) {
        Type[] interfaces = TestMySuperGenericType.class.getGenericInterfaces();
        for(Type type : interfaces){
            Type[] gTypes = ((ParameterizedType)type).getActualTypeArguments();
            for(Type gType : gTypes){
                System.out.println("Generic type:"+gType.toString());
            }
        }
    }
}

interface GenericClass<T> {
    public void print(T obj);
}

interface TestMySuperGenericType extends GenericClass<Integer> {
}
1
Anil Agrawal

Cette question est ancienne, mais le mieux est d’utiliser Google Gson.

Un exemple pour obtenir la personnalisation viewModel.

Class<CustomViewModel<String>> clazz = new GenericClass<CustomViewModel<String>>().getRawType();
CustomViewModel<String> viewModel = viewModelProvider.get(clazz);

Classe de type générique

class GenericClass<T>(private val rawType: Class<*>) {

    constructor():this(`$Gson$Types`.getRawType(object : TypeToken<T>() {}.getType()))

    fun getRawType(): Class<T> {
        return rawType as Class<T>
    }
}
0
Vahe Gharibyan

En fait, je suppose que vous avez un champ dans votre classe de type T. S'il n'y a pas de champ de type T, à quoi ça sert d'avoir un type générique? Donc, vous pouvez simplement faire une instance de ce champ.

Dans mon cas, j'ai un

Liste <T> articles;
if (items.get (0) instanceof Locality) ... 

Bien entendu, cela ne fonctionne que si le nombre total de classes possibles est limité.

0
Christine