如何获取泛型类型参数?

3

简单来说:

public static class MyClass<T> {
    // i don't want to keep an instance of T, if it is not necessary.
    // and it is not nice, not neat.

    // Or, let's say, the member are in the form of :
    ArrayList<T> mArrayList = new ArrayList<T>();
    // the problem of getting the generic type parameter is still present.
}

@Test
public final void test() {
    MyClass<Integer> myObject = new MyClass<Integer>();
    getParamType( myObject );
}

private static final <T> void getParamType(final MyClass<T> _myObject) {
    System.out.println(_myObject.getClass().getTypeParameters()[0]);    // T
    System.out.println(((T) new Object()).getClass());                  // class java.lang.Object
}

如何让代码打印出?

我知道stackoverflow上有很多关于这个问题的帖子(并回答),但它们都不能解决这个问题。

  • 我不知道为什么有些人需要调用getGenericSuperclass() - 因为在这种简单情况下没有涉及到继承。
  • 而且我也无法将其强制转换为ParameterizedType

.

System.out.println((ParameterizedType) _myObject.getClass());
// Compile Error: Cannot cast from Class<capture#11-of ? extends TreeTest2.MyClass> to ParameterizedType

System.out.println((ParameterizedType) _myObject.getClass().getGenericSuperclass());
// Runtime Exception: java.lang.ClassCastException

根据@Thomas的指南,我找到了一个解决方法来获取class java.lang.Integer

首先,在测试代码中,我们创建一个匿名的(必须是匿名的)MyClass<T>子类。(这很奇怪。为什么它只支持子类?)

@Test
public final void test() {
    MyClass<Integer> myObject = new MyClass<Integer>() {};  // Anonymous sub-class
    getParamType( myObject );
}

然后我们可以使用getGenericSuperclass()方法获取一个Type,然后将其转换为ParameterizedType,接着使用getActualTypeArguments()

private static final <T> void getParamType(final MyClass<T> _myObject) {
    System.out.println( ((ParameterizedType) _myObject.getClass().getGenericSuperclass()).getActualTypeArguments()[0] );
}

它可以完美地打印出class java.lang.Integer

这不是很好,因为测试代码应该模拟实际情况,用户最可能不会不停地创建无意义的子类。

这种方法基于TypeReference class的想法。但我不知道如何使用它。我已经尝试过class MyClass<T> extends TypeReference<T>。但我仍然需要创建MyClass<T> 的子类才能使TypeReference.getType()打印class java.lang.Integer

请帮忙,感谢任何的输入,因为最佳方法还没有出现。


基于上述解决方法的进一步问题:为什么只有匿名子类起作用?

public static class SubMyClass<T> extends MyClass<T>{}

@Test
public final void test() {
    MyClass<Integer> myObject = new MyClass<Integer>() {};  // Anonymous sub-class
    getParamType( myObject );               // class java.lang.Integer

    MyClass<Integer> mySubObject = new SubMyClass<Integer>();   // named sub-class
    getParamType( mySubObject );            // T
}

(MyClassgetParamType()保持不变。)
3个回答

3

这有点困难,因为Java故意不能做到这一点("类型擦除")。

解决方法被称为超级类型令牌。关于此还有一些在SO上的帖子(比如这个那个)。


谢谢 Thomas!TypeReference 类对我来说是新的。我可以这样理解 TypeReference<T> 为什么可以获取其 T,因为 (1) 它是抽象的,因此强制继承,以及 (2) 只有 Type java.lang.Class.getGenericSuperclass() 可以转换为 ParameterizedType,因此我们可以使用 getActualTypeArguments - midnite
如果有一个像Type java.lang.Class.getGeneric_____class()这样的方法,我们就可以使用((ParameterizedType) getClass().getGeneric____class()).getActualTypeArguments(),那将是非常好的,不是吗?顺便问一下,为什么Java只有返回Type的“获取超类”方法呢? - midnite
回到最初的问题。如何通过TypeReference获取class java.lang.Integer?我可以通过new TypeReference<List<String>>() {}.getType()来获得java.util.List<java.lang.String>(但这并不有用,因为它并没有从对象中获取)。然后我使用class MyClass<T> extends TypeReference<T>,通过myObject.getType()只能得到T。感谢指引我这个方向。请帮助我更进一步。 - midnite

2
当你有这样的问题时,你应该问自己,如果没有泛型,你会怎么做?因为任何使用泛型的程序都可以转换成一个等价的不使用泛型的程序。(这个转换被称为类型擦除。)所以,如果你不能在没有泛型的情况下完成它,那么你也不能在有泛型的情况下完成它。
没有泛型的程序如下所示:
public static class MyClass {
    ArrayList mArrayList = new ArrayList();
}

@Test
public final void test() {
    MyClass myObject = new MyClass();
    Integer result = getParamType( myObject ); // how would you implement getParamType()?
}

感谢您的回复。将Class<T>作为成员变量,我理解为什么它能够工作。但是我不明白相反的情况。就像有一个成员变量public T mField;一样,我们也可以之后从mField.getClass()中检索到T - midnite
ж­¤е¤–пјЊдёєд»Ђд№€еЏЄжњ‰getGenericSuperclass()иЂЊжІЎжњ‰getGenericThisclass()пјџиї™дјјд№ЋдёЌе¤Єеђ€зђ†гЂ‚ - midnite
1
@midnite:泛型只是一种编译时的类型检查机制。它只是一种幻觉,没有真正的实体存在。你会要求类型参数,仅仅因为你在这种幻觉中看到了它。我向你展示的是当你剥去这个幻觉时它真正的样子,而你需要的却是别的东西。记住最重要的一点是:当你在代码中加入泛型时,它永远也不会给你创造出你之前不能做到的神奇能力。这就是为什么考虑如何在没有泛型的情况下做到同样效果是很有启示性的原因。 - newacct
我喜欢你的想法。这很鼓舞人心。 - midnite
好的...我承认泛型是一种幻觉。而且我可以想象泛型是编译时的东西。但是...对于反射来说,它不遵守这个规则,是吗? - midnite
显示剩余3条评论

1

Java有一个被误解的特性叫做类型擦除,它专门阻止你这样做。

泛型参数信息在运行时不存在。

相反,你可以手动接受一个Class<T>


谢谢回复。您说的“手动接受类<T>”是什么意思? - midnite
1
在你的构造函数中添加一个 Class<T> 参数。 - SLaks
2
擦除是一种妥协。我不会把它称作误导。 - Ben Schulz
我们是否必须在创建对象时调用 MyClass<Integer> myObject = new MyClass<Integer>(Integer.class);?这很危险,因为我们也可以调用 MyClass<Integer> myObject = new MyClass<Integer>(String.class);,而这也是有效的。 - midnite
2
如果您将参数声明为 Class<T>,那么就不会出现这种情况了。 - barfuin
@Thomas 哦,是的!我的错误 :-x - midnite

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