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


J'ai un cours de 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 pas appeler T.class.

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


513
2017-08-09 06:58


origine


Réponses:


La réponse courte est, qu'il n'y a aucun moyen de trouver 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 à cela est de passer le Class du paramètre de type dans le constructeur du type générique, par ex.

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

440
2017-08-09 07:03



Je cherchais un moyen de le faire moi-même sans ajouter une dépendance supplémentaire au classpath. Après une enquête, je l'ai trouvé est possible tant que vous avez un supertype générique. 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, alors c'est l'approche la plus nette à mon humble avis.

La plupart des cas d'utilisation de médicaments 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 Java pure

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

Solution de printemps

Mon projet utilisait Printemps ce qui est encore mieux que Spring a une méthode utilitaire pratique pour trouver le type. C'est la meilleure approche pour moi car elle semble la plus propre. 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 ";
    }

179
2018-02-08 22:14



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

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

(Notez les doubles accolades à la fin.)

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

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

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

Ce n'est 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;
    }
  }
}

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

78
2018-04-16 05:51



Une approche / solution / solution standard consiste à ajouter class objet 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
2017-08-09 07:07



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.class dans la classe Foo en sélectionnant le Type (de bert bruynooghe réponse) et l'inférer en utilisant Class exemple:

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 de multiples calculs à ce sujet. L'une des utilisations typiques est dans 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 d'une classe Foo dans une autre fonction ou à partir d'une classe Bar.


17
2018-03-26 23:20



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: Peut être utilisé uniquement en tant que superclasse

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

OU

  1. Doit être créé comme implémentation anonyme (new Generic<Integer>() {};)

10
2017-11-30 11:09



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

8
2017-10-21 02:41



Vous ne pouvez pas le faire à cause de l'effacement de type. Voir aussi Stack Overflow question Génériques Java - effacement de type - quand et qu'est-ce qui se passe.


8
2017-08-09 07:02