我想知道如何获得由程序员在使用泛型时编写的运行时类型。例如,如果我有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
),并且从Integer
到Object
的强制转换成功,正如它应该的那样。
事实上,正如这里已经删除的答案所提到的,您可以通过一种称为Gafter's Gadget的技术利用这种"残余"类型信息,您可以使用ParameterizedType
上的getActualTypeArguments()
方法访问该技术。
这个小工具的工作方式是创建一个参数化类型的空子类,例如new TypeToken<String>() {}
。由于这里的匿名类是一个具体类型的子类(这里没有类型参数T
,它被一个实际类型String
所取代),该类型的方法必须能够返回实际类型(在本例中为String
)。使用反射,您可以发现该类型:在本例中,getActualTypeParameters()[0]
将返回String.class
。
Gafter的Gadget可以扩展到任意复杂的参数化类型,并且实际上经常被做大量反射和泛型工作的框架使用。例如,Google Guice依赖注入框架有一种叫做TypeLiteral
的类型,它正是用于这个目的。