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

Java reflection get runtime type when using generics

我想知道如何获取程序员在使用泛型时编写的运行时类型。例如,如果我有 class Main<T extends List<String>> 而程序员写了类似

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

我如何理解使用 class 扩展 List<String> 的反射?

我很好奇我怎样才能做到这一点。随着

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

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

正如上面的评论所指出的,由于 type erasure 你不能这样做。但在评论中,后续问题是:

I know that the generics are removed after compilation, but I am wondering how then ClassCastException is thrown runtime ? Sorry, if this is a stupid question, but how it knows to throws this exception if there isn't any information about classes.

答案是,虽然type参数从type中删除了,但它仍然保留在bytecode.[=36中=]

从本质上讲,编译器将其转换为:

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相关联,但它仍然出现(以class强制转换指令的形式)。如果在运行时类型不同,您将得到 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 的技术利用此 "vestigial" 类型信息,您可以使用 getActualTypeArguments() ParameterizedType.

上的方法

小工具的工作方式是创建一个参数化类型的空子class,例如new TypeToken<String>() {}。由于这里的匿名class是一个具体类型的子class(这里没有类型参数T,它被替换为真实类型,String)方法该类型必须能够 return 真实类型(在本例中为 String)。使用反射你可以发现那个类型:在这种情况下,getActualTypeParameters()[0] 会 return String.class.

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