Java反射在使用泛型时获得运行时类型



我想知道如何获得由程序员在使用泛型时编写的运行时类型。例如,如果我有class Main<T extends List<String>>,程序员写一些像

这样的东西
Main<ArrayList<String>> main = new Main<>();

我如何理解使用反射哪个类扩展List<String>是使用的?

我只是好奇如何才能做到这一点。

main.getClass().getTypeParameters()[0].getBounds[] 

我只能理解边界类(不是运行时类)

正如上面的注释所指出的,由于类型擦除,您不能这样做。但在评论中,接下来的问题是:

我知道泛型编译后被删除,但我想知道ClassCastException是如何抛出运行时?对不起,如果这是一个愚蠢的问题,但是如果没有任何关于类的信息,它是如何知道抛出这个异常的。

答案是,虽然类型参数从类型中删除,但它仍然保留在字节码中。

本质上,编译器将这个转换为:

List<String> list = new ArrayList<>();
list.add("foo");
String value = list.get(0);

这:

List list = new ArrayList();
list.add("foo");
String value = (String) list.get(0); // note the cast!

这意味着类型String不再与字节码中的类型ArrayList相关联,但它仍然出现(以类强制转换指令的形式)。如果在运行时类型不同,你将得到一个ClassCastException

这也解释了为什么你可以这样做:

// The next line should raise a warning about raw types
// if compiled with Java 1.5 or newer
List rawList = new ArrayList();
// Since this is a raw List, it can hold any object.
// Let's stick a number in there.
rawList.add(new Integer(42));
// This is an unchecked conversion. Not always wrong, but always risky.
List<String> stringList = rawList;
// You'd think this would be an error. But it isn't!
Object value = stringList.get(0);

事实上,如果你尝试一下,你会发现你可以安全地将42的值拉回Object,而不会有任何错误。这样做的原因是编译器在这里没有插入到String的强制转换—它只是插入到Object的强制转换(因为左边的类型只是Object),并且从IntegerObject的强制转换成功,正如它应该的那样。

无论如何,这只是用一种冗长的方式来解释类型擦除不会擦除对给定类型的所有引用,而只擦除类型参数本身。

事实上,正如这里已经删除的答案所提到的,您可以通过一种称为Gafter's Gadget的技术利用这种"残余"类型信息,您可以使用ParameterizedType上的getActualTypeArguments()方法访问该技术。

这个小工具的工作方式是创建一个参数化类型的空子类,例如new TypeToken<String>() {}。由于这里的匿名类是一个具体类型的子类(这里没有类型参数T,它被一个实际类型String所取代),该类型的方法必须能够返回实际类型(在本例中为String)。使用反射,您可以发现该类型:在本例中,getActualTypeParameters()[0]将返回String.class

Gafter的Gadget可以扩展到任意复杂的参数化类型,并且实际上经常被做大量反射和泛型工作的框架使用。例如,Google Guice依赖注入框架有一种叫做TypeLiteral的类型,它正是用于这个目的。

相关内容

  • 没有找到相关文章

最新更新