我想知道如何获得程序员在使用泛型时编写的运行时类型。例如,如果我有class Main<T extends List<String>>
并且程序员写了类似
Main<ArrayList<String>> main = new Main<>();
如何使用反射来理解使用哪个类List<String>
?
我只是好奇我怎样才能做到这一点。随着
main.getClass().getTypeParameters()[0].getBounds[]
我只能理解边界类(不是运行时类)。
答案 0 :(得分:5)
正如上面的评论所指出的那样,由于type erasure你无法做到这一点。但在评论中,后续问题是:
我知道编译后删除了泛型,但是我想知道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的技术利用此“残留”类型信息,您可以使用getActualTypeArguments()
方法访问ParameterizedType
{1}}。
小工具的工作方式是创建参数化类型的空子类,例如: new TypeToken<String>() {}
。由于此处的匿名类是具体类型的子类(此处没有类型参数T
,它已被实际类型替换,String
)类型上的方法必须能够返回真实类型(在这种情况下为String
)。使用反射,您可以发现该类型:在这种情况下,getActualTypeParameters()[0]
将返回String.class
。
Gafter的小工具可以扩展到任意复杂的参数化类型,并且实际上经常被使用反射和泛型做很多工作的框架使用。例如,Google Guice依赖注入框架有一个名为TypeLiteral
的类型,它正是为此目的服务的。