如何在 Java 泛型中获取类型变量的类

Posted

技术标签:

【中文标题】如何在 Java 泛型中获取类型变量的类【英文标题】:How to get the class of type variable in Java Generics 【发布时间】:2013-02-26 08:40:30 【问题描述】:

我看到过类似的问题,但它们并没有太大帮助。

例如我有这个通用类:

public class ContainerTest<T>


    public void doSomething()
    
        //I want here to determinate the Class of the type argument (In this case String)
    

和另一个使用这个容器类的类

public class TestCase


    private ContainerTest<String> containerTest;

    public void someMethod()
    
        containerTest.doSomething();
    

是否可以确定方法 doSomething() 中类型参数的类 没有显式类型 变量/字段 或任何 构造函数 在 ContainerTest 类中?

更新:ContainerTest 类的格式更改

【问题讨论】:

if (t instanceof String) ? 我已经更新了问题 你不能只将类类型作为参数传递吗? 我可能会迟到,但有一个很好的解决方案。使用 C# (: 【参考方案1】:

唯一的方法是将类存储在实例变量中,并要求它作为构造函数的参数:

public class ContainerTest<T>

    private Class<T> tClass;
    public ContainerTest(Class<T> tClass) 
        this.tCLass = tClass;
    

    public void doSomething()
    
        //access tClass here
    

【讨论】:

这正是我会做的。 你确定这是唯一的方法吗? 除非你强制调用者继承你的类,是的。但这通常不是问题……【参考方案2】:

如果您对反射方式感兴趣,我在这篇很棒的文章中找到了部分解决方案:http://www.artima.com/weblogs/viewpost.jsp?thread=208860

简而言之,你可以使用java.lang.Class.getGenericSuperclass()java.lang.reflect.ParameterizedType.getActualTypeArguments() 方法,但是你必须继承一些父超类。

以下 sn-p 适用于 直接 扩展超类 AbstractUserType 的类。有关更通用的解决方案,请参阅参考文章。

import java.lang.reflect.ParameterizedType;


public class AbstractUserType<T> 

    public Class<T> returnedClass() 
        ParameterizedType parameterizedType = (ParameterizedType) getClass()
                .getGenericSuperclass();

        @SuppressWarnings("unchecked")
        Class<T> ret = (Class<T>) parameterizedType.getActualTypeArguments()[0];

        return ret;
    

    public static void main(String[] args) 
        AbstractUserType<String> myVar = new AbstractUserType<String>() ;

        System.err.println(myVar.returnedClass());
    


【讨论】:

【参考方案3】:

没有从类中获取通用类型参数的“干净”方法。 相反,一种常见的模式是将通用类型的类传递给构造函数,并将其作为内部属性保留,就像在 java.util.EnumMap 实现中所做的那样。

http://docs.oracle.com/javase/1.5.0/docs/api/java/util/EnumMap.html http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/EnumMap.java

public class ContainerTest<T> 

    Class<T> type;
    T t;

    public ContainerTest(Class<T> type) 
        this.type = type;
    

    public void setT(T t) 
        this.t = t;
    

    public T getT() 
        return t;
    

    public void doSomething() 
        //There you can use "type" property.
    

【讨论】:

这被称为“类型令牌” @ZeDonDino 我链接 EnumMap 以表明即使是 OpenJDK 也在标准实现中使用这种模式。【参考方案4】:

没有。这是不可能的,因为type erasure(类型参数编译为 Object + 类型转换)。如果您真的需要在运行时知道/强制执行类型,您可以存储对Class 对象的引用。

public class ContainerTest<T> 
   private final Class<T> klass;
   private final List<T> list = new ArrayList<T>();

   ContainerTest(Class<T> klass) 
     this.klass = klass;
   

   Class<T> getElementClass() 
     return klass;
   

   void add(T t) 
      //klass.cast forces a runtime cast operation
      list.add(klass.cast(t));
   

用途:

ContainerTest<String> c = new ContainerTest<>(String.class);

【讨论】:

谨慎使用,仅在 Java SE 7 及更高版本中受支持。 ContainerTest c = new ContainerTest(String.class) //于是钻石诞生了 嗯,我知道这一点。我希望有一种方法可以将类从 中取出,而不是将值传递给构造函数或字段或类似【参考方案5】:

一种方法来获取类型参数的运行时类型,通过使用 Guava 的TypeToken 来捕获它。该解决方案的缺点是每次需要Container 的实例时都必须创建一个匿名子类。

class Container<T> 

    TypeToken<T> tokenOfContainedType = new TypeToken<T>(getClass()) ;

    public Type getContainedType() 
        return tokenOfContainedType.getType();
    


class TestCase 

    // note that containerTest is not a simple instance of Container,
    // an anonymous subclass is created
    private Container<String> containerTest = new Container<String>() ;

    @Test
    public void test() 
        Assert.assertEquals(String.class, containerTest.getContainedType());
    

这个解决方案的关键在上面代码中使用的TypeToken的构造函数的JavaDoc中有描述:

客户端创建一个空的匿名子类。这样做会将类型参数嵌入到匿名类的类型层次结构中,因此我们可以在运行时重构它,尽管会被擦除。

【讨论】:

哇,一票否决,太好了……你能至少评论一下你为什么不喜欢它吗? 这是一个有趣的解决方案,但我认为它更适合像番石榴一样的反射,而不是这个问题的用例。这个解决方案的问题是它需要在您实例化它的每个位置创建一个匿名类。 (注意我不是反对者)【参考方案6】:

要了解T 的值,您需要通过继承ContainerTest 在类型定义中捕获它:

class MyStringClass extends ContainerTest<String> 

如果你愿意,你也可以使用匿名类来做到这一点:

ContainerTest<String> myStringClass = new ContainerTest<String>();

那么解析T的值,可以使用TypeTools:

Class<?> stringType = TypeResolver.resolveRawArgument(ContainerTest.class, myStringClass.getClass());
assert stringType == String.class;

【讨论】:

【参考方案7】:

如果你可以这样定义

public class ContainerTest<T>


    public void doSomething(T clazz)
    

    

那么有可能

【讨论】:

我想不带任何参数地调用这个方法。

以上是关于如何在 Java 泛型中获取类型变量的类的主要内容,如果未能解决你的问题,请参考以下文章

java 父类如何在运行期动态获取子类类名

java - 如何在Java泛型中返回类对象“类型”? [复制]

Java泛型中通配符的使用

java 怎么获取list泛型里的对象类型

java 泛型中的T和?

Java泛型中T和问号(通配符)的区别