以下Java方法编译失败:
<T extends Number> void foo(T t)
{
Class<? extends T> klass = t.getClass();
}
收到的错误是:类型不匹配:无法从Class<capture#3-of ? extends Number>
转换为Class<? extends T>
谁能解释为什么Class<? extends T>
无效,但Class<? extends Number>
是好的?
Javadoc说:
实际结果类型是
Class<? extends |X|>
,其中|X|是对调用getClass的表达式的静态类型的擦除。例如,在以下代码片段中不需要强制类型转换:
Number n = 0;
Class<? extends Number> c = n.getClass();
因为T
类的类型不从T
扩展。相反,它从Number
扩展,正如您在<T extends Number>
中声明的那样。就是这么简单。
一个更好的问题应该是:
为什么下面的代码不能编译?
<T extends Number> void foo(T t) { Class<T> class1 = t.getClass(); }
答案是Object#getClass()
返回Class<?>
和一个无界通配符?
,因为对象本身并不直接意识到它的泛型类型,这是在任意方法中所期望的。
这有点傻。对于大多数用例,如果x.getClass()
返回Class<? extends X>
,而不是擦除的Class<? extends |X|>
,那会更好。
擦除是导致信息丢失的原因,使代码在表面上安全的情况下无法编译。t.getClass()
返回Class<? extends |T|>
, |T| = Number
,所以它返回Class<? extends Number>
。
擦除(由语言规范强制)是为了保持理论正确性。例如
List<String> x = ...;
Class<List> c1 = x.getClass(); // ok
Class<List<String>> c2 = x.getClass(); // error
虽然c2看起来非常合理,但在Java中,List<String>
实际上没有这样的类。只有List
的类。所以允许c2在理论上是不正确的。
这种形式在实际使用中产生了许多问题,程序员可以推断Class<? extends X>
对他们的目的是安全的,但必须处理已擦除的版本。
您可以简单地定义自己的getClass
,返回未擦除类型
static public <X> Class<? extends X> getFullClass(X x)
return (Class<? extends X>)(Class) x.getClass() ;
<T extends Number> void foo(T t)
{
Class<? extends T> klass = getFullClass(t);
}