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

DPM*_*DPM 3 java generics reflection

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

Main<ArrayList<String>> main = new Main<>();
Run Code Online (Sandbox Code Playgroud)

我如何通过反射来了解使用了哪个类扩展List<String>

我很好奇我怎么能做到这一点。用

main.getClass().getTypeParameters()[0].getBounds[] 
Run Code Online (Sandbox Code Playgroud)

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

Dan*_*den 5

正如上面的注释所指出的,由于类型擦除,您无法执行此操作。但是在评论中,后续问题是:

我知道编译后会删除泛型,但是我想知道ClassCastException如何在运行时抛出?抱歉,如果这是一个愚蠢的问题,但是如果没有有关类的任何信息,它将如何抛出该异常。

答案是,虽然类型参数从擦除类型,它仍然在字节码

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

List<String> list = new ArrayList<>();
list.add("foo");
String value = list.get(0);
Run Code Online (Sandbox Code Playgroud)

到这个:

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

这意味着该类型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);
Run Code Online (Sandbox Code Playgroud)

确实,如果您尝试一下,您会发现可以安全地将42值作为撤回,Object并且完全没有任何错误。这样做的原因是,编译器没有将强制类型转换插入到String此处-而是只插入了强制类型转换到Object(因为左侧类型为just Object),并且强制转换IntegerObject成功。

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

实际上,作为此处提到的已删除答案,您可以通过称为Gafter's Gadget的技术来利用这种“调查”类型的信息,您可以使用上的getActualTypeArguments()方法进行访问ParameterizedType

小工具的工作方式是创建一个参数化类型的空子类,例如new TypeToken<String>() {}。由于此处的匿名类是具体类型的子类(此处没有类型参数T,已由实型替换String),类型上的方法必须能够返回实型(在这种情况下String)。使用反射,您可以发现该类型:在这种情况下,getActualTypeParameters()[0]将返回String.class

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