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

700

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

Quelle est la meilleure façon de contourner le problème en utilisant T.class?

robinmag
la source
2
Essayez les réponses à cette question, je pense que c'est similaire. stackoverflow.com/questions/1942644/…
Emil
3
doublon possible de Get type générique de classe à l'exécution
7hi4g0
1
doublon possible d' Instanciation d'une classe générique en Java
matiasg
1
import com.fasterxml.jackson.core.type.TypeReference; new TypeReference<T>(){}
Bogdan Shulga

Réponses:

569

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 des types dans le Tutoriel Java pour plus de détails.

Une solution populaire à cela consiste à passer le Classparamètre du 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
    }
}
Zsolt Török
la source
73
Cette réponse fournit une solution valide mais il est inexact de dire qu'il n'y a aucun moyen de trouver le type générique au moment de l'exécution. Il s'avère que l'effacement de type est beaucoup plus complexe qu'un effacement de couverture. Ma réponse vous montre comment obtenir le type générique des classes.
Ben Thurley
3
@BenThurley Neat trick, mais pour autant que je sache, cela ne fonctionne que s'il existe un super-type générique à utiliser. Dans mon exemple, vous ne pouvez pas récupérer le type de T dans Foo <T>.
Zsolt Török
@webjockey Non, vous ne devriez pas. Assigner le typeParameterClasssans une affectation par défaut dans le constructeur est parfaitement bien. Il n'est pas nécessaire de le régler une deuxième fois.
Adowrath
C'est la première solution qui vient à l'esprit, mais parfois ce n'est pas vous qui créerez / initierez des objets. Vous ne pourrez donc pas utiliser de constructeur. Par exemple lors de la récupération des entités JPA de la base de données.
Paramvir Singh Karwal
233

Je cherchais un moyen de le faire moi-même sans ajouter de dépendance supplémentaire au chemin de classe. Après quelques recherches , je trouve qu'il est possible aussi longtemps que vous avez un générique supertype. C'était OK pour moi car je travaillais avec un DAO couche avec un super-type de couche générique. Si cela correspond à votre scénario, c'est l'approche la plus soignée à mon humble avis.

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

Solution Java pure

L'article Accès aux types génériques lors de l'exécution en Java explique comment vous pouvez le faire en utilisant du Java pur.

@SuppressWarnings("unchecked")
public GenericJpaDao() {
  this.entityBeanType = ((Class) ((ParameterizedType) getClass()
      .getGenericSuperclass()).getActualTypeArguments()[0]);
}

Solution de printemps

Mon projet utilisait Spring, ce qui est encore mieux car Spring a une méthode utilitaire pratique pour trouver le type. C'est la meilleure approche pour moi car elle semble la plus soignée. 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 ";
    }
Ben Thurley
la source
1
s'il vous plaît de préciser le sens des resolveTypeArgument arguments
gstackoverflow
getClass () est une méthode de java.lang.Object qui renverra la classe de l'objet spécifique au moment de l'exécution, c'est l'objet pour lequel vous voulez résoudre le type. AbstractHibernateDao.class n'est que le nom de la classe de base ou de la superclasse de la hiérarchie des classes de types génériques. L'instruction d'importation est incluse, vous devriez donc pouvoir trouver facilement les documents et vérifier vous-même. Ceci est la page docs.spring.io/spring/docs/current/javadoc-api/org/…
Ben Thurley
Quelle est la solution de ressort avec la version 4.3.6 et supérieure. Cela ne fonctionne pas avec le ressort 4.3.6.
Erlan
1
Le lien dans "Pure Java solution" est cassé, c'est maintenant blog.xebia.com/acessing-generic-types-at-runtime-in-java
Nick Breen
1
@ AlikElzin-kilaka il est initialisé dans le constructeur en utilisant la classe Spring GenericTypeResolver.
Ben Thurley
103

Il y a cependant une petite faille: si vous définissez votre Fooclasse comme abstraite. Cela signifie que vous devez instancier votre classe en tant que:

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

(Notez les accolades doubles à la fin.)

Vous pouvez maintenant récupérer le type de Tlors de l'exécution:

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

Notez cependant que cela mySuperclassdoit être la superclasse de la définition de classe définissant réellement le type final pour T.

Il n'est pas non plus 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;
    }
  }
}

Alors:

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 );
    }
}
Bert bruynooghe
la source
5
C'est facilement la meilleure réponse ici! En outre, pour ce que cela vaut, c'est la stratégie que Google Guice utilise pour les classes de liaison avecTypeLiteral
ATG
14
Notez que chaque fois que cette méthode de construction d'objet est utilisée, une nouvelle classe anonyme est créée. En d' autres termes, deux objets aet bcréé cette façon à la fois étendre la même classe , mais pas les classes d'instance identiques. a.getClass() != b.getClass()
Martin Serrano
3
Il y a un scénario dans lequel cela ne fonctionne pas. Si Foo devait implémenter une interface, telle que Serializable, alors la classe anonyme ne serait pas Serializable à moins que la classe ne soit instanciée. J'ai essayé de contourner ce problème en créant une classe d'usine sérialisable qui crée la classe anonyme dérivée de Foo, mais pour une raison quelconque, getActualTypeArguments renvoie le type générique au lieu de la classe réelle. Par exemple: (new FooFactory <MyType> ()). CreateFoo ()
Lior Chaga
38

Une approche / solution / solution standard consiste à ajouter un classobjet 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();
    }
 }
Andreas Dolk
la source
1
Mais il semble que @autowired ne puisse pas être utilisé dans les faits, comment contourner le problème?
Alfred Huang
@AlfredHuang La solution serait de créer un bean pour la classe qui fait cela et de ne pas s'appuyer sur le câblage automatique.
Calebj
20

Imaginez que vous ayez 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.classdans la classe Foo en sélectionnant la Type(à partir de la réponse de bert bruynooghe) et en la déduisant en utilisant Classinstance:

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 dans l'implémentation DAO générique.

L'implémentation 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 invoquée à partir de la classe Foo dans une autre fonction ou de la classe Bar.

droidpl
la source
1
toString().split(" ")[1]c'était le problème, évitez les"class "
IgniteCoders
16

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 <> ");
    }
} 

REMARQUES: ne peut être utilisé qu'en superclasse

  1. Doit être étendu avec typed class ( Child extends Generic<Integer>)

OU

  1. Doit être créé en tant qu'implémentation anonyme ( new Generic<Integer>() {};)
Yaroslav Kovbas
la source
3
getTypeName appelle toString, donc peut être remplacé par .getActualTypeArguments () [0] .toString ();
Yaroslav Kovbas
9

Une meilleure route que la classe suggérée par les autres est de passer 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>());
Ricky Clarkson
la source
@ Ricky Clarkson: Je ne vois pas comment cette usine devrait retourner des foos paramétrés. Pourriez-vous expliquer comment vous obtenez Foo <T> de cela? Il me semble que cela ne donne que du Foo non paramétré. Le T dans make10 n'est-il pas simplement Foo ici?
ib84
@ ib84 J'ai corrigé le code; Il me semble que Foo a été paramétré lorsque j'ai écrit la réponse à l'origine.
Ricky Clarkson
9

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 sur la classe dérivée:

class Bar extends Foo<Whatever> {
    @Override
    Class<T> getTClass() {
        return Whatever.class;
    }
}
user1154664
la source
Oui, mais je voudrais laisser un minimum qui doit être fait lors de l'extension de cette classe. Vérifiez la réponse de
droidpl
5

J'ai une solution (laide mais efficace) pour ce problème, que j'ai utilisée 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 __()
    {
    }
}
unknown6656
la source
3

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 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)

    } 

Vous pouvez ensuite 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, 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];
    }
}
Juan Urrego
la source
2

Comme expliqué dans d'autres réponses, pour utiliser ce ParameterizedType approche, 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-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 de nouvelle classe. (Notez le {} à la fin ... étendu, mais n'écrasez 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();
Riaan Schutte
la source
2

Je suppose que, puisque vous avez une classe générique, vous auriez une variable comme ça:

private T t;

(cette variable doit prendre une valeur chez le constructeur)

Dans ce cas, vous pouvez simplement créer la méthode suivante:

Class<T> getClassOfInstance()
{
    return (Class<T>) t.getClass();
}

J'espère que cela aide!

Pontios
la source
1
   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
        }
   }
Abel ANEIROS
la source
1

Si vous étendez ou implémentez une classe / interface utilisant 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 Lorsque votre classe implémente 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);
}

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

Cas 3 Lorsque votre interface étend 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> {
}
Anil Agrawal
la source
1

C'est assez simple. Si vous avez besoin de la même classe:

Class clazz = this.getClass();
ParameterizedType parameterizedType = (ParameterizedType) clazz.getGenericSuperclass();
try {
        Class typeClass = Class.forName( parameterizedType.getActualTypeArguments()[0].getTypeName() );
        // You have the instance of type 'T' in typeClass variable

        System.out.println( "Class instance name: "+  typeClass.getName() );
    } catch (ClassNotFoundException e) {
        System.out.println( "ClassNotFound!! Something wrong! "+ e.getMessage() );
    }
Gana
la source
0

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, quel est l'intérêt 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> éléments;
dans ma classe, et je vérifie si le type de classe est "Localité" par

if (items.get (0) instanceof Locality) ...

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

Christine
la source
4
Que dois-je faire si items.isEmpty () est vrai?
chaotic3quilibrium
0

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

Un exemple pour devenir personnalisé 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>
    }
}
Vahe Gharibyan
la source
0

Je voulais passer T.class à une méthode qui utilise des génériques

La méthode readFile lit un fichier .csv spécifié par fileName avec fullpath. Il peut y avoir des fichiers csv avec des contenus différents, donc je dois passer la classe de fichier modèle afin que je puisse obtenir les objets appropriés. Puisque c'est la lecture du fichier csv, je voulais le faire de manière générique. Pour une raison ou une autre, aucune des solutions ci-dessus n'a fonctionné pour moi. Je dois utiliser Class<? extends T> typepour le faire fonctionner. J'utilise la bibliothèque opencsv pour analyser les fichiers CSV.

private <T>List<T> readFile(String fileName, Class<? extends T> type) {

    List<T> dataList = new ArrayList<T>();
    try {
        File file = new File(fileName);

        Reader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
        Reader headerReader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));

        CSVReader csvReader = new CSVReader(headerReader);
        // create csv bean reader
        CsvToBean<T> csvToBean = new CsvToBeanBuilder(reader)
                .withType(type)
                .withIgnoreLeadingWhiteSpace(true)
                .build();

        dataList = csvToBean.parse();
    }
    catch (Exception ex) {
        logger.error("Error: ", ex);
    }

    return dataList;
}

C'est ainsi que la méthode readFile est appelée

List<RigSurfaceCSV> rigSurfaceCSVDataList = readSurfaceFile(surfaceFileName, RigSurfaceCSV.class);
Madhu Tomy
la source
-4

J'utilise une solution de contournement pour cela:

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

MyClass myClassInstance = MyClass.class.newInstance();
nikolai.serdiuk
la source