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

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类的格式。


如果 (t instanceof String) - vikingsteve
你不能只将类类型作为参数传递吗? - Rohit Jain
我可能会迟到,但有一个很好的解决方案。使用C# (: - Elgirhath
6个回答

19
唯一的方法是将类存储在实例变量中,并将其作为构造函数的参数要求:
public class ContainerTest<T>
{
    private Class<T> tClass;
    public ContainerTest(Class<T> tClass) {
        this.tCLass = tClass;
    }

    public void doSomething()
    {
        //access tClass here
    }
}

1
这正是我会做的。 - Bob Wang
3
你确定这是唯一的方法吗? - vach
1
除非你强制调用者子类化你的类,否则是不会有问题的。但通常这不是一个问题... - Didier L

12

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

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

下面的代码片段适用于直接扩展超类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());
    }

}

7

没有一种“干净”的方式可以从类内部获取泛型类型参数。 相反,常见的模式是将泛型类型的 Class 传递给构造函数,并将其保留为内部属性,就像在 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.
    }
}

1
这被称为“类型标记”。 - hertzsprung
2
@ZeDonDino 我链接 EnumMap 来展示即使是 OpenJDK 也在标准实现中使用这种模式。 - Renaud

5

不可能,因为由于类型擦除(类型参数编译为对象+类型转换),所以无法实现。如果您确实需要在运行时了解或强制执行类型,则可以存储对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);

2
请谨慎使用此功能,仅支持Java SE 7及更高版本。 - ZeDonDino
ContainerTest<String> c = new ContainerTest<String>(String.class) //于是钻石诞生了 - Javier
我知道这一点。我希望有一种方法可以从<String>中获取Class,而不是将值传递到构造函数或字段中或类似的地方。 - ZeDonDino

3

使用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中描述:
客户端创建一个空的匿名子类。这样做会将类型参数嵌入到匿名类的类型层次结构中,因此我们可以在运行时重新构建它,尽管类型擦除存在。

哇,被踩了,真好...你能否至少留下一条评论,说明你为什么不喜欢它? - zagyi
这是一个有趣的解决方案,但我认为它更适合像guava一样的反射,而不是这个问题的用例。这种解决方案的问题在于它需要在每个实例化它的位置创建一个匿名类。(请注意,我不是那个给你点踩的人) - Didier L

-2

如果你可以这样定义

public class ContainerTest<T>
{

    public void doSomething(T clazz)
    {

    }
}

那么就有可能了


我想调用这个方法而不带任何参数。 - ZeDonDino

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接