web-dev-qa-db-fra.com

Obtenir le type générique de classe à exécuter

Comment puis-je atteindre cet objectif?

public class GenericClass<T>
{
    public Type getMyType()
    {
        //How do I return the type of T?
    }
}

Tout ce que j'ai essayé jusqu'à présent renvoie toujours le type Object plutôt que le type spécifique utilisé.

411
Glenn

Comme d'autres l'ont mentionné, ce n'est possible que par réflexion dans certaines circonstances.

Si vous avez vraiment besoin du type, il s'agit du modèle de solution de contournement habituel (adapté au type):

public class GenericClass<T> {

     private final Class<T> type;

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

     public Class<T> getMyType() {
         return this.type;
     }
}
271
Henning

J'ai vu quelque chose comme ça

private Class<T> persistentClass;

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

dans le hibernate GenericDataAccessObjects Example

221
FrVaBe

Les génériques ne sont pas reifiés au moment de l'exécution. Cela signifie que les informations ne sont pas présentes au moment de l'exécution.

Ajouter des génériques à Java tout en maintenant la compatibilité avec les versions antérieures était un tour de force (vous pouvez voir le document fondamental à son sujet: Assurer l'avenir du passé: ajouter de la généricité au langage de programmation Java ).

Il existe une littérature abondante sur le sujet, et certaines personnes sont insatisfaites avec l'état actuel, d'autres disent qu'en réalité c'est un { leurre } _ et qu'il n'est pas réellement nécessaire. Vous pouvez lire les deux liens, je les ai trouvés assez intéressants.

83
ewernli

Utilisez la goyave.

import com.google.common.reflect.TypeToken;
import Java.lang.reflect.Type;

public abstract class GenericClass<T> {
  private final TypeToken<T> typeToken = new TypeToken<T>(getClass()) { };
  private final Type type = typeToken.getType(); // or getRawType() to return Class<? super T>

  public Type getType() {
    return type;
  }

  public static void main(String[] args) {
    GenericClass<String> example = new GenericClass<String>() { };
    System.out.println(example.getType()); // => class Java.lang.String
  }
}

Il y a quelque temps, j'ai posté des exemples complets, y compris des classes abstraites et des sous-classes ici .

Remarque: pour cela, vous devez instancier une sous-classe sur GenericClass pour pouvoir associer correctement le paramètre type. Sinon, le type est simplement retourné sous la forme T.

50
Cody A. Ray

Sûr que vous pouvez.

Java n'utilise pas l'information au moment de l'exécution, pour des raisons de compatibilité avec les versions antérieures. Mais les informations sont réellement présentes sous forme de métadonnées et sont accessibles par réflexion (mais elles ne sont toujours pas utilisées pour la vérification de type).

De l'API officielle:

http://download.Oracle.com/javase/6/docs/api/Java/lang/reflect/ParameterizedType.html#getActualTypeArguments%28%29

Cependant, pour votre scénario, je n’utiliserais pas la réflexion. Personnellement, je suis plus enclin à utiliser cela pour le code cadre. Dans votre cas, je voudrais simplement ajouter le type en tant que paramètre constructeur.

36
Joeri Hendrickx

Les génériques Java sont principalement lors de la compilation, cela signifie que les informations de type sont perdues lors de l'exécution.

class GenericCls<T>
{
    T t;
}

sera compilé à quelque chose comme

class GenericCls
{
   Object o;
}

Pour obtenir les informations de type au moment de l'exécution, vous devez les ajouter en tant qu'argument du ctor.

class GenericCls<T>
{
     private Class<T> type;
     public GenericCls(Class<T> cls)
     {
        type= cls;
     }
     Class<T> getType(){return type;}
}

Exemple:

GenericCls<?> instance = new GenericCls<String>(String.class);
assert instance.getType() == String.class;
33
josefx

J'ai utilisé l'approche suivante:

public class A<T> {

    protected Class<T> clazz;

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

    public Class<T> getClazz() {
        return clazz;
    }
}

public class B extends A<C> {
   /* ... */
    public void anything() {
       // here I may use getClazz();
    }
}
21
Moesio

La technique décrite dans ce article de Ian Robertson fonctionne pour moi.

En bref exemple rapide et sale:

 public abstract class AbstractDAO<T extends EntityInterface, U extends QueryCriteria, V>
 {
    /**
     * Method returns class implementing EntityInterface which was used in class
     * extending AbstractDAO
     *
     * @return Class<T extends EntityInterface>
     */
    public Class<T> returnedClass()
    {
        return (Class<T>) getTypeArguments(AbstractDAO.class, getClass()).get(0);
    }

    /**
     * Get the underlying class for a type, or null if the type is a variable
     * type.
     *
     * @param type the type
     * @return the underlying class
     */
    public static Class<?> getClass(Type type)
    {
        if (type instanceof Class) {
            return (Class) type;
        } else if (type instanceof ParameterizedType) {
            return getClass(((ParameterizedType) type).getRawType());
        } else if (type instanceof GenericArrayType) {
            Type componentType = ((GenericArrayType) type).getGenericComponentType();
            Class<?> componentClass = getClass(componentType);
            if (componentClass != null) {
                return Array.newInstance(componentClass, 0).getClass();
            } else {
                return null;
            }
        } else {
            return null;
        }
    }

    /**
     * Get the actual type arguments a child class has used to extend a generic
     * base class.
     *
     * @param baseClass the base class
     * @param childClass the child class
     * @return a list of the raw classes for the actual type arguments.
     */
    public static <T> List<Class<?>> getTypeArguments(
            Class<T> baseClass, Class<? extends T> childClass)
    {
        Map<Type, Type> resolvedTypes = new HashMap<Type, Type>();
        Type type = childClass;
        // start walking up the inheritance hierarchy until we hit baseClass
        while (!getClass(type).equals(baseClass)) {
            if (type instanceof Class) {
                // there is no useful information for us in raw types, so just keep going.
                type = ((Class) type).getGenericSuperclass();
            } else {
                ParameterizedType parameterizedType = (ParameterizedType) type;
                Class<?> rawType = (Class) parameterizedType.getRawType();

                Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
                TypeVariable<?>[] typeParameters = rawType.getTypeParameters();
                for (int i = 0; i < actualTypeArguments.length; i++) {
                    resolvedTypes.put(typeParameters[i], actualTypeArguments[i]);
                }

                if (!rawType.equals(baseClass)) {
                    type = rawType.getGenericSuperclass();
                }
            }
        }

        // finally, for each actual type argument provided to baseClass, determine (if possible)
        // the raw class for that type argument.
        Type[] actualTypeArguments;
        if (type instanceof Class) {
            actualTypeArguments = ((Class) type).getTypeParameters();
        } else {
            actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments();
        }
        List<Class<?>> typeArgumentsAsClasses = new ArrayList<Class<?>>();
        // resolve types by chasing down type variables.
        for (Type baseType : actualTypeArguments) {
            while (resolvedTypes.containsKey(baseType)) {
                baseType = resolvedTypes.get(baseType);
            }
            typeArgumentsAsClasses.add(getClass(baseType));
        }
        return typeArgumentsAsClasses;
    }
  }
14
Ondrej Bozek

Je ne pense pas que vous puissiez le faire, Java utilise la suppression de type lors de la compilation afin que votre code soit compatible avec les applications et les bibliothèques créées avant les génériques.

À partir des documents Oracle:

Type d'effacement

Les génériques ont été introduits dans le langage Java pour fournir un type plus étroit vérifie au moment de la compilation et prend en charge la programmation générique. À implémenter des génériques, le compilateur Java applique un effacement de type à:

Remplacez tous les paramètres de type des types génériques par leurs bornes ou Object si les paramètres de type ne sont pas liés. Le bytecode produit, par conséquent, ne contient que des classes, interfaces et méthodes ordinaires . Insérer des caractères si nécessaire pour préserver la sécurité du type. Produire Méthodes de pontage pour préserver le polymorphisme dans les types génériques étendus . La suppression de type garantit qu'aucune nouvelle classe n'est créée pour paramétré les types; par conséquent, les génériques n'engendrent aucune surcharge d'exécution.

http://docs.Oracle.com/javase/tutorial/Java/generics/erasure.html

12
Dimitar
public abstract class AbstractDao<T>
{
    private final Class<T> persistentClass;

    public AbstractDao()
    {
        this.persistentClass = (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass())
                .getActualTypeArguments()[0];
    }
}
9
Ali Yeganeh

Ceci est ma solution:

import Java.lang.reflect.Type;
import Java.lang.reflect.TypeVariable;

public class GenericClass<T extends String> {

  public static void main(String[] args) {
     for (TypeVariable typeParam : GenericClass.class.getTypeParameters()) {
      System.out.println(typeParam.getName());
      for (Type bound : typeParam.getBounds()) {
         System.out.println(bound);
      }
    }
  }
}
8
Matthias M

Je pense qu'il y a une autre solution élégante.

Ce que vous voulez faire est de "passer" (en toute sécurité) le type du paramètre de type générique de la classe concerete à la superclasse.

Si vous vous permettez de considérer le type de classe comme "métadonnées" sur la classe, cela suggère la méthode Java pour coder les métadonnées au moment de l'exécution: les annotations.

Commencez par définir une annotation personnalisée selon ces lignes:

import Java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EntityAnnotation {
    Class entityClass();
}

Vous pouvez ensuite avoir à ajouter l'annotation à votre sous-classe.

@EntityAnnotation(entityClass =  PassedGenericType.class)
public class Subclass<PassedGenericType> {...}

Ensuite, vous pouvez utiliser ce code pour obtenir le type de classe dans votre classe de base:

import org.springframework.core.annotation.AnnotationUtils;
.
.
.

private Class getGenericParameterType() {
    final Class aClass = this.getClass();
    EntityAnnotation ne = 
         AnnotationUtils.findAnnotation(aClass, EntityAnnotation.class);

    return ne.entityClass();
}

Certaines limites de cette approche sont:

  1. Vous spécifiez le type générique (PassedGenericType) dans deux emplacements plutôt que dans un emplacement non-DRY.
  2. Cela n'est possible que si vous pouvez modifier les sous-classes concrètes.
8
DaBlick

Voici la 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
 
2. Doit être créé en tant qu'implémentation anonyme (new Generic<Integer>() {};

5
Yaroslav Kovbas

Une solution simple pour cela peut être comme ci-dessous

public class GenericDemo<T>{
    private T type;

    GenericDemo(T t)
    {
        this.type = t;
    }

    public String getType()
    {
        return this.type.getClass().getName();
    }

    public static void main(String[] args)
    {
        GenericDemo<Integer> obj = new  GenericDemo<Integer>(5);
        System.out.println("Type: "+ obj.getType());
    }
}
3
Paul92

Voici un moyen que j'ai dû utiliser une ou deux fois:

public abstract class GenericClass<T>{
    public abstract Class<T> getMyType();
}

De même que

public class SpecificClass extends GenericClass<String>{

    @Override
    public Class<String> getMyType(){
        return String.class;
    }
}
3
PJWeisberg

Tu ne peux pas. Si vous ajoutez une variable membre de type T à la classe (vous n'avez même pas besoin de l'initialiser), vous pouvez l'utiliser pour récupérer le type.

3
andrewmu

Juste au cas où vous utiliseriez stocker une variable en utilisant le type générique, vous pouvez facilement résoudre ce problème en ajoutant une méthode getClassType comme suit:

public class Constant<T> {
  private T value;

  @SuppressWarnings("unchecked")
  public Class<T> getClassType () {
    return ((Class<T>) value.getClass());
  }
}

J'utilise plus tard l'objet de classe fourni pour vérifier s'il s'agit d'une instance d'une classe donnée, comme suit:

Constant<?> constant = ...;
if (constant.getClassType().equals(Integer.class)) {
    Constant<Integer> integerConstant = (Constant<Integer>)constant;
    Integer value = integerConstant.getValue();
    // ...
}
2
Pablo Trinidad

Pour compléter certaines des réponses ici, j'ai dû obtenir le ParametrizedType de MyGenericClass, quelle que soit la hiérarchie, à l'aide de la récursivité:

private Class<T> getGenericTypeClass() {
        return (Class<T>) (getParametrizedType(getClass())).getActualTypeArguments()[0];
}

private static ParameterizedType getParametrizedType(Class clazz){
    if(clazz.getSuperclass().equals(MyGenericClass.class)){ // check that we are at the top of the hierarchy
        return (ParameterizedType) clazz.getGenericSuperclass();
    } else {
        return getParametrizedType(clazz.getSuperclass());
    }
}
2
galex

Voici ma solution

public class GenericClass<T>
{
    private Class<T> realType;

    public GenericClass() {
        findTypeArguments(getClass());
    }

    private void findTypeArguments(Type t) {
        if (t instanceof ParameterizedType) {
            Type[] typeArgs = ((ParameterizedType) t).getActualTypeArguments();
            realType = (Class<T>) typeArgs[0];
        } else {
            Class c = (Class) t;
            findTypeArguments(c.getGenericSuperclass());
        }
    }

    public Type getMyType()
    {
        // How do I return the type of T? (your question)
        return realType;
    }
}

Quel que soit le niveau de votre hiérarchie de classe, Cette solution fonctionne toujours, par exemple:

public class FirstLevelChild<T> extends GenericClass<T> {

}

public class SecondLevelChild extends FirstLevelChild<String> {

}

Dans ce cas, getMyType () = Java.lang.String

1
Lin Yu Cheng

Voici mon astuce:

public class Main {

    public static void main(String[] args) throws Exception {

        System.out.println(Main.<String> getClazz());

    }

    static <T> Class getClazz(T... param) {

        return param.getClass().getComponentType();
    }

}
1
HRgiger
public static final Class<?> getGenericArgument(final Class<?> clazz)
{
    return (Class<?>) ((ParameterizedType) clazz.getGenericSuperclass()).getActualTypeArguments()[0];
}
0
kayz1