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

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

Quelle est la meilleure façon de la contourner en utilisant T.class ?

533
demandé sur Peter Mortensen 2010-08-09 10:58:45

17 réponses

la réponse courte est, qu'il n'y a aucun moyen de découvrir 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 de type dans le tutoriel Java pour plus de détails.

une solution populaire est de passer le Class du paramètre type au 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
    }
}
454
répondu Zsolt Török 2010-08-09 07:09:48

je cherchais un moyen de le faire moi-même sans ajouter une dépendance supplémentaire au chemin de classe. Après une enquête, j'ai constaté que est possible tant que vous avez un supertype Générique. C'était OK pour moi car je travaillais avec un calque DAO avec un supertype de calque Générique. Si cela correspond à votre scénario, alors C'est la meilleure approche IMHO.

la plupart des génériques utilisent des cas que je suis venu les across ont une sorte de supertype générique , par exemple List<T> pour ArrayList<T> ou GenericDAO<T> pour DAO<T> , etc.

Pure Java solution

l'article accéder aux types génériques à l'exécution en Java explique comment vous pouvez le faire en utilisant pur Java.

solution de Printemps

mon projet utilisait printemps qui est même mieux que le ressort a une méthode utilitaire pratique pour trouver le type. C'est la meilleure approche pour moi car il semble plus élégante. Je suppose que si vous n'utilisiez pas Spring vous pourriez écrire votre propre méthode utilitaire.

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 ";
    }
182
répondu Ben Thurley 2017-10-02 11:17:15

il y a cependant une petite faille: si vous définissez votre classe Foo comme abstraite. Cela signifie que vous devez instancier votre classe comme:

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

(notez les accolades doubles à la fin.)

maintenant vous pouvez récupérer le type de T à l'exécution:

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

noter toutefois que mySuperclass doit être la superclasse de la définition de la classe définissant effectivement le type final pour T .

Il n'est également pas très élégant, 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;
    }
  }
}

puis:

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 );
    }
}
79
répondu bert bruynooghe 2017-08-07 11:37:59

une approche/solution standard consiste à ajouter un objet class au(X) constructeur (s), comme:

 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();
    }
 }
33
répondu Andreas_D 2012-02-09 17:37:20

Imaginez que vous avez une superclasse abstraite qui est 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 la réponse de Bert bruynooghe) et en l'inférant en utilisant 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éal, il est donc un bonne idée de mettre en cache la valeur calculée pour éviter de multiples calculs à ce sujet. L'une des utilisations typiques est la mise en œuvre générique de L'OAC.

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 retournée est Bar.classe lorsqu'elle est invoquée à partir de la classe Foo dans une autre fonction ou de la classe Bar.

17
répondu droidpl 2014-12-24 10:32:07

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: ne peut être utilisé que comme superclasse

  1. doit être étendu avec la classe dactylographiée ( Child extends Generic<Integer> )

ou

  1. doit être créé comme mise en œuvre anonyme ( new Generic<Integer>() {}; )
10
répondu Yaroslav Kovbas 2018-06-17 08:55:59

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

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

et plus tard sur la classe dérivée:

class Bar extends Foo<Whatever> {
    @Override
    Class<T> getTClass() {
        return Whatever.class;
    }
}
8
répondu user1154664 2014-06-05 23:36:38

vous ne pouvez pas le faire à cause de l'effacement de type. Voir aussi Débordement de Pile question Java génériques de type erasure - quand et ce qui se passe .

8
répondu Konrad Garus 2017-05-23 12:18:30

une meilleure route que la classe suggérée par les autres est de passer dans un objet qui peut 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>());
7
répondu Ricky Clarkson 2013-03-14 13:04:00

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 reflétant les génériques .

4
répondu Peter Tseng 2015-09-27 08:33:03

j'ai une solution (laide mais efficace) pour 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 __()
    {
    }
}
4
répondu Unknown6656 2017-11-07 06:20:34

j'ai trouvé un moyen générique et simple de le faire. Dans ma classe, j'ai créé une méthode qui retourne le type générique selon sa position dans la définition de classe. Supposons une définition de classe comme ceci:

public class MyClass<A, B, C> {

}

maintenant, créons quelques attributs pour persister 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)

    } 

alors vous pouvez créer une méthode générique qui retourne le type basé sur 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 il suffit d'appeler la méthode et d'envoyer 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
répondu Juan Urrego 2018-01-03 14:21:01

comme expliqué dans d'autres réponses, pour utiliser cette approche ParameterizedType , vous devez étendre la classe, mais cela ressemble à un travail supplémentaire pour faire une toute nouvelle classe qui l'étend...

ainsi, rendre la classe abstraite il vous force à l'étendre, satisfaisant ainsi l'exigence de sous-classement. (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 pour l'étendre sans définir une nouvelle classe. (Note de l' {} sur la fin... étendu, mais ne pas les écraser quelque chose - sauf si vous voulez).

private ConfigurationDefinition<String> myConfigA = new ConfigurationDefinition<String>(...){};
private ConfigurationDefinition<File> myConfigB = new ConfigurationDefinition<File>(...){};
...
Class stringType = myConfigA.getType();
Class fileType = myConfigB.getType();
2
répondu Riaan Schutte 2017-09-20 03:01:24
   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
répondu Abel ANEIROS 2014-04-02 12:45:55

si vous étendez ou implémentez une classe/interface qui utilise des génériques , vous pouvez obtenir le Type générique de classe/interface parent, sans modifier aucune classe/interface existante.

il pourrait y avoir trois possibilités,

Cas 1 Lorsque votre classe étend 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 Quand votre classe met en œuvre une interface qui utilise 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 3 Lorsque votre interface est l'extension d'une interface qui utilise des génériques

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
répondu Anil Agrawal 2016-12-05 10:43:57

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 bon avoir un Type générique? Ainsi, vous pouvez simplement faire un instanceof sur ce domaine.

dans mon cas, j'ai un

List<T> items;
dans ma classe, et je vérifie si le type de classe est "Locality" par
if (items.get(0) instanceof Locality) ...

bien sûr, cela ne fonctionne que si le nombre total de classes possibles est limité.

0
répondu Christine 2011-11-11 11:51:32

j'utilise une solution de contournement pour ceci:

class MyClass extends Foo<T> {
....
}

MyClass myClassInstance = MyClass.class.newInstance();
-4
répondu nikolai.serdiuk 2017-06-27 08:45:27