在运行时获取类的泛型类型

2022-01-29 06:44:51 标签 javagenericsreflection

我怎样才能做到这一点?

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

在hibernate GenericDataAccessObjects Example中

###泛型在运行时不被具体化。这意味着该信息在运行时不存在。

在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编译器应用类型擦除:

将泛型类型中的所有类型参数替换为它们的边界或

对象,如果类型参数是无界的。生成的字节码

因此只包含普通的类、接口和方法。

如果需要,插入类型强制转换以保持类型安全。生成

在扩展的泛型类型中保持多态性的桥接方法。

类型擦除确保没有为参数化创建新类

类型;因此泛型不会产生运行时开销。

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

我认为还有另一种优雅的解决方案。

你要做的是(安全的)将泛型类型参数的类型从concerte类向上传递到超类。

如果您允许自己将类类型视为类上的“元数据”,则可以使用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();
}

这种方法的一些局限性是:

指定泛型类型(PassedGenericType) in TWO places rather than on),而不是一个没有干旱的地方。

这只有在您可以修改具体的子类时才可能实现。这是我的解决方案:

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

###这个出租车的一个简单的解决方案如下

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

这是可行的解决方案!!

@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。 必须扩展类型化类(子类扩展泛型<Integer>)

2。 必须创建为匿名实现(新的Generic<Integer>() {};)

# # #你不能。如果将类型T的成员变量添加到类中(甚至不需要初始化它),就可以使用它来恢复类型。

###为了完成这里的一些答案,我必须得到MyGenericClass的参数化类型,无论递归的帮助是多么高的层次结构:

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

###如果你使用使用泛型类型存储一个变量,你可以很容易地解决这个问题,添加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];
}

这是我的解决方案。下面的例子应该可以解释这一点。唯一的要求是子类必须设置泛型类型而不是对象。

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

###如果你有这样一个类:

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

使用变量T,你可以打印T的名字:

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

###如果你正在使用spring:

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

顺便说一下,GenericTypeResolver仍然会得到空的非子类类,就像前面提到的问题,因为此类类的泛型信息在编译后被完全删除。

解决这个问题的唯一方法可能是:

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

###我做了和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();
    }
}

我想这也可以通过使用类定义对象作为源来实现,这只是一个单独的用例。

我认为关键在于很多其他的答案都以这样或那样的方式说过你需要在运行时获得类型信息使其在运行时可用;对象本身维护它们的类型,但擦除(也像其他人说的那样,使用适当的引用)会导致任何封闭/容器类型丢失该类型信息。

###

It might be useful to someone. You can Use 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);
    }
}
阅读全文

▼ 版权说明

相关文章也很精彩
推荐内容
更多标签
相关热门
全站排行
随便看看

错说 cuoshuo.com —— 程序员的报错记录

部分内容根据CC版权协议转载;网站内容仅供参考,生产环境使用务必查阅官方文档

辽ICP备19011660号-5

×

扫码关注公众号:职场神器
发送: 1
获取永久解锁本站全部文章的验证码