繁体   English   中英

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

[英]Java reflection get runtime type when using generics

我想知道如何获得程序员在使用泛型时编写的运行时类型。 例如,如果我有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的小工具可以扩展到任意复杂的参数化类型,并且实际上经常被使用反射和泛型做大量工作的框架使用。 例如,Google Guice依赖注入框架有一个名为TypeLiteral的类型,它正是为此目的服务的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM