簡體   English   中英

在運行時獲取 class 的泛型類型

[英]Get generic type of class at runtime

我怎樣才能做到這一點?

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

到目前為止,我嘗試過的所有東西總是返回類型Object而不是使用的特定類型。

正如其他人所提到的,只有在某些情況下通過反射才有可能。

如果您真的需要該類型,這是通常的(類型安全)解決方法模式:

public class GenericClass<T> {

     private final Class<T> type;

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

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

我見過這樣的事情

private Class<T> persistentClass;

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

休眠的 GenericDataAccessObjects示例中

泛型在運行時沒有具體化 這意味着信息在運行時不存在。

在保持向后兼容性的同時向 Java 添加泛型是一項艱巨的任務(您可以查看有關它的開創性論文:讓未來對過去安全:為 Java 編程語言添加泛型)。

這方面的文獻非常豐富,有人對現狀不滿,有人說其實是個誘餌,沒有必要。 你可以閱讀這兩個鏈接,我發現它們很有趣。

使用番石榴。

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

不久前,我在這里發布了一些完整的示例,包括抽象類和子類。

注意:這要求您實例化GenericClass子類,以便它可以正確綁定類型參數。 否則它只會將類型返回為T

Java 泛型大多是編譯時的,這意味着類型信息在運行時會丟失。

class GenericCls<T>
{
    T t;
}

將被編譯成類似

class GenericCls
{
   Object o;
}

要在運行時獲取類型信息,您必須將其添加為 ctor 的參數。

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

例子:

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

你當然可以。

出於向后兼容性的原因,Java 在運行時不使用這些信息。 但是這些信息實際上是作為元數據存在的,可以通過反射訪問(但它仍然不用於類型檢查)。

來自官方 API:

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

但是,對於您的情況,我不會使用反射。 我個人更傾向於將其用於框架代碼。 在您的情況下,我只需將類型添加為構造函數參數。

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

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

我使用了以下方法:

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

我不認為您可以,Java 在編譯時使用類型擦除,因此您的代碼與創建前泛型的應用程序和庫兼容。

來自 Oracle 文檔:

類型擦除

泛型被引入 Java 語言以在編譯時提供更嚴格的類型檢查並支持泛型編程。 為了實現泛型,Java 編譯器將類型擦除應用於:

如果類型參數是無界的,則將泛型類型中的所有類型參數替換為其邊界或 Object。 因此,生成的字節碼只包含普通的類、接口和方法。 必要時插入類型轉換以保持類型安全。 生成橋方法以保留擴展泛型類型中的多態性。 類型擦除確保不會為參數化類型創建新類; 因此,泛型不會產生運行時開銷。

http://docs.oracle.com/javase/tutorial/java/generics/erasure.html

Ian Robertson 在這篇文章中描述的技術對我有用。

簡而言之,快速而骯臟的例子:

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

我認為還有另一個優雅的解決方案。

您想要做的是(安全地)將泛型類型參數的類型從具體類“傳遞”到超類。

如果您允許自己將類類型視為類上的“元數據”,則建議使用 Java 方法在運行時對元數據進行編碼:注解。

首先按照這些思路定義一個自定義注解:

import java.lang.annotation.*;

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

然后,您必須將注釋添加到您的子類。

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

然后您可以使用此代碼獲取基類中的類類型:

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

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

    return ne.entityClass();
}

這種方法的一些限制是:

  1. 您在兩個地方指定泛型類型 ( PassedGenericType ),而不是在一個非 DRY 的地方。
  2. 這只有在您可以修改具體子類時才有可能。

這是我的解決方案:

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

這是一種方法,我不得不使用一次或兩次:

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

隨着

public class SpecificClass extends GenericClass<String>{

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

這是有效的解決方案!!!

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

注意:只能用作超類
1. 必須使用類型化類進行擴展( Child extends Generic<Integer>
或者
2. 必須創建為匿名實現( new Generic<Integer>() {};

你不能。 如果您將 T 類型的成員變量添加到類中(您甚至不必初始化它),您可以使用它來恢復類型。

該駕駛室的一個簡單解決方案如下

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

為了完成這里的一些答案,我必須借助遞歸獲得 MyGenericClass 的 ParametrizedType,無論層次結構有多高:

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

這是我的解決方案

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

無論您的類層次結構有多少級別,此解決方案仍然有效,例如:

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

}

public class SecondLevelChild extends FirstLevelChild<String> {

}

在這種情況下,getMyType() = java.lang.String

這是我的竅門:

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

}

這是我的解決方案。 這些例子應該解釋它。 唯一的要求是子類必須設置泛型類型,而不是對象。

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.HashMap;
import java.util.Map;

public class TypeUtils {

    /*** EXAMPLES ***/

    public static class Class1<A, B, C> {

        public A someA;
        public B someB;
        public C someC;

        public Class<?> getAType() {
            return getTypeParameterType(this.getClass(), Class1.class, 0);
        }

        public Class<?> getCType() {
            return getTypeParameterType(this.getClass(), Class1.class, 2);
        }
    }

    public static class Class2<D, A, B, E, C> extends Class1<A, B, C> {

        public B someB;
        public D someD;
        public E someE;
    }

    public static class Class3<E, C> extends Class2<String, Integer, Double, E, C> {

        public E someE;
    }

    public static class Class4 extends Class3<Boolean, Long> {

    }

    public static void test() throws NoSuchFieldException {

        Class4 class4 = new Class4();
        Class<?> typeA = class4.getAType(); // typeA = Integer
        Class<?> typeC = class4.getCType(); // typeC = Long

        Field fieldSomeA = class4.getClass().getField("someA");
        Class<?> typeSomeA = TypeUtils.getFieldType(class4.getClass(), fieldSomeA); // typeSomeA = Integer

        Field fieldSomeE = class4.getClass().getField("someE");
        Class<?> typeSomeE = TypeUtils.getFieldType(class4.getClass(), fieldSomeE); // typeSomeE = Boolean


    }

    /*** UTILS ***/

    public static Class<?> getTypeVariableType(Class<?> subClass, TypeVariable<?> typeVariable) {
        Map<TypeVariable<?>, Type> subMap = new HashMap<>();
        Class<?> superClass;
        while ((superClass = subClass.getSuperclass()) != null) {

            Map<TypeVariable<?>, Type> superMap = new HashMap<>();
            Type superGeneric = subClass.getGenericSuperclass();
            if (superGeneric instanceof ParameterizedType) {

                TypeVariable<?>[] typeParams = superClass.getTypeParameters();
                Type[] actualTypeArgs = ((ParameterizedType) superGeneric).getActualTypeArguments();

                for (int i = 0; i < typeParams.length; i++) {
                    Type actualType = actualTypeArgs[i];
                    if (actualType instanceof TypeVariable) {
                        actualType = subMap.get(actualType);
                    }
                    if (typeVariable == typeParams[i]) return (Class<?>) actualType;
                    superMap.put(typeParams[i], actualType);
                }
            }
            subClass = superClass;
            subMap = superMap;
        }
        return null;
    }

    public static Class<?> getTypeParameterType(Class<?> subClass, Class<?> superClass, int typeParameterIndex) {
        return TypeUtils.getTypeVariableType(subClass, superClass.getTypeParameters()[typeParameterIndex]);
    }

    public static Class<?> getFieldType(Class<?> clazz, AccessibleObject element) {
        Class<?> type = null;
        Type genericType = null;

        if (element instanceof Field) {
            type = ((Field) element).getType();
            genericType = ((Field) element).getGenericType();
        } else if (element instanceof Method) {
            type = ((Method) element).getReturnType();
            genericType = ((Method) element).getGenericReturnType();
        }

        if (genericType instanceof TypeVariable) {
            Class<?> typeVariableType = TypeUtils.getTypeVariableType(clazz, (TypeVariable) genericType);
            if (typeVariableType != null) {
                type = typeVariableType;
            }
        }

        return type;
    }

}

萬一您使用泛型類型存儲變量,您可以通過添加 getClassType 方法輕松解決此問題,如下所示:

public class Constant<T> {
  private T value;

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

我稍后使用提供的類對象來檢查它是否是給定類的實例,如下所示:

Constant<?> constant = ...;
if (constant.getClassType().equals(Integer.class)) {
    Constant<Integer> integerConstant = (Constant<Integer>)constant;
    Integer value = integerConstant.getValue();
    // ...
}
public static final Class<?> getGenericArgument(final Class<?> clazz)
{
    return (Class<?>) ((ParameterizedType) clazz.getGenericSuperclass()).getActualTypeArguments()[0];
}

如果你有這樣的課程:

public class GenericClass<T> {
    private T data;
}

使用T變量,然后您可以打印T名稱:

System.out.println(data.getClass().getSimpleName()); // "String", "Integer", etc.

如果您正在使用彈簧:

public static Class<?>[] resolveTypeArguments(Class<?> parentClass, Class<?> subClass) {
    if (subClass.isSynthetic()) {
        return null;
    }
    return GenericTypeResolver.resolveTypeArguments(subClass, parentClass);
}

順便說一句,對於非子類類, GenericTypeResolver仍然會為 null,就像提到的問題一樣,因為此類類的通用信息在編譯后被完全刪除

解決這個問題的唯一方法可能是:

public class GenericClass<T>
{
    private final Class<T> clazz;
    public Foo(Class<T> clazz) {
        this.clazz= clazz;
    }
    
    public Type getMyType()
    {
        return clazz;
    }
}

如果您無法更改泛型類並使用本頁已說明的方法之一,那么簡單的方法是根據運行時實例類名稱獲取類型類。

Class getType(GenericType runtimeClassMember){
if (ClassA.class.equals(runtimeClassMember.getClass()){
  return TypeForClassA.class;
} else if (ClassB.class.equals(runtimeClassMember.getClass()){
  return TypeForClassB.class;
} 

//throw an expectation or do whatever you want for the cases not described in the if section.
}

使用返回類類型的抽象方法,然后在該類中使用它,並且無論您在何處擴展泛型類,都必須實現該抽象方法以返回所需的類類型

public class AbsractService<T>{
  public abstract Class<T> getClassType ();
   .......
}

在運行時

class AnimalService extends AbstractService<MyType>{
    
public Class<MyType> getClassType (){
        return MyType.class;
 }

  .....
}

即使在復雜的類型層次結構上, TypeTools也可以解析類型 arguments。 但是為了解析泛型類型 arguments,它們必須在 class 或接口定義中進行編碼。 前任:

class StringClass extends GenericClass<String> {}

Class<?> typeArg = TypeResolver.resolveRawArgument(GenericClass.class, StringClass.class);

assert typeArg == String.class;

我和上面的@Moesio 做了同樣的事情,但在 Kotlin 中可以這樣做:

class A<T : SomeClass>() {

    var someClassType : T

    init(){
    this.someClassType = (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0] as Class<T>
    }

}

這是受到 Pablo 和 CoolMind 的回答的啟發。 有時我也使用了 kayz1 的答案中的技術(也表達在許多其他答案中),我相信這是一種體面且可靠的方式來完成 OP 的要求。

我首先選擇將其定義為接口(類似於 PJWeisberg),因為我有可以從該功能中受益的現有類型,特別是異構通用聯合類型:

public interface IGenericType<T>
{
    Class<T> getGenericTypeParameterType();
}

我在通用匿名接口實現中的簡單實現如下所示:

//Passed into the generic value generator function: toStore
//This value name is a field in the enclosing class.
//IUnionTypeValue<T> is a generic interface that extends IGenericType<T>
value = new IUnionTypeValue<T>() {
    ...
    private T storedValue = toStore;
    ...
    
    @SuppressWarnings("unchecked")
    @Override
    public Class<T> getGenericTypeParameterType()
    {
        return (Class<T>) storedValue.getClass();
    }
}

我想這也可以通過使用類定義對象作為源來構建,這只是一個單獨的用例。 我認為關鍵是正如許多其他答案所說,以一種或另一種方式,您需要在運行時獲取類型信息才能在運行時使用它; 對象本身保持它們的類型,但是擦除(也正如其他人所說,使用適當的引用)會導致任何封閉/容器類型丟失該類型信息。

它可能對某人有用。 您可以使用 java.lang.ref.WeakReference; 這邊走:

class SomeClass<N>{
  WeakReference<N> variableToGetTypeFrom;

  N getType(){
    return variableToGetTypeFrom.get();
  }
}

我發現這是一個簡單易懂且易於解釋的解決方案

public class GenericClass<T> {

    private Class classForT(T...t) {
        return t.getClass().getComponentType();
    }

    public static void main(String[] args) {
        GenericClass<String> g = new GenericClass<String>();

        System.out.println(g.classForT());
        System.out.println(String.class);
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM