当我在使用仿制药时,我发现了一些东西。在下面的示例中,doStuff1
编译但doStuff2
不编译:
public <T extends Foo> void doStuff1(T value) {
Class<? extends Foo> theClass = value.getClass();
}
public <T extends Foo> void doStuff2(T value) {
Class<? extends T> theClass = value.getClass();
}
所以,我查看了Object.getClass()
的文档,发现了这个:
实际结果类型是Class&lt;? extends | X |&gt;其中| X |是擦除调用getClass的表达式的静态类型。
这让我有点好奇。为什么getClass()
以这种方式设计?如果适用的话,我可以理解将类型转换为它们的原始类,但我认为没有明显的理由说明为什么它们必须使它同时杀掉T
。有没有一个特定的原因,它也可以摆脱它,或者它只是一个普遍的“让我们摆脱一切因为它更容易;谁还需要它”呢?
答案 0 :(得分:9)
如果getClass()
返回Class<? extends X>
,则不会发生任何真正的错误;实际上它会帮助很多用例。
唯一的问题是,它在理论上是不正确的。如果某个对象是ArrayList<String>
,则其class
不能是Class<ArrayList<String>>
- 没有这样的类,只有Class<ArrayList>
。
这实际上与擦除无关。如果有一天Java获得完整的具体类型,getClass()
仍应返回Class<? extends |X|>
;但是应该有一个新的方法,比如getType()
,它可以返回更详细的Type<? extends X>
。 (但是,getType
可能与许多现有的类使用自己的getType
方法冲突)
对于时间安排,由于Class<? extends X>
在很多情况下可能有用,我们可以设计自己的方法来做到这一点
static <X> Class<? extends X> myGetClass(X x){ ... }
但是可以理解他们不会在标准库中使用这种黑客。
答案 1 :(得分:0)
请考虑以下程序:
var a = new ArrayList<String>();
var b = new ArrayList<Integer>();
var aType = a.getClass();
var bType = b.getClass();
if (aType == bType) {
...
}
如果执行此操作,aType
和bType
将包含相同的运行时类对象(因为通用类型的所有实例共享相同的运行时类),并且if语句的主体将执行
它也可以编译。特别是,aType
和bType
的声明类型都是Class<? extends ArrayList>
,比较两个兼容类型的引用对编译器是有意义的。
但是,如果将getClass
定义为返回Class<? extends T>
,则aType
的编译时间类型将为Class<? extends ArrayList<String>
,而bType
的类型将为是Class<? extends ArrayList<Integer>>
。由于Java将泛型定义为不变型,因此它们是不可转换的类型,因此即使运行时认为比较正确,也要求编译器拒绝比较aType == bType
毫无意义。
由于要解决此问题,因此需要对Java类型系统进行重大扩展,因此声明getClass
返回擦除可能被视为是更简单的选项,即使它在其他情况下也会引起违反直觉的行为,例如您遇到的那个。当然,以这种方式定义后,就不能在不破坏API的情况下对其进行更改...
要变通解决此问题,您可以使用未经检查的演员表:
@SuppressWarnings("unchecked")
<T> Class<? extends T> getTypedClassOf(T t) {
return (Class) t.getClass();
}
当然,这意味着您可能需要诉诸未经检查的强制转换,以使编译器了解不可转换类型的类对象毕竟可能是相同的...
答案 2 :(得分:-1)
您无法确定value
实际上是否为T,它也可能是T的子类。getClass()
会返回value
的“真实”类,但您可以'确保是T,因此必须阅读Class<? extends T>
!这与T是类型参数这一事实无关。
[编辑] 好吧不太,我没有意识到method2没有编译。我认为这是一个编译器问题。我得到了
Type mismatch: cannot convert from Class<capture#1-of ? extends Foo> to Class<? extends T>
我认为编译器只会意识到T对象是Foo。编译器应该知道getClass()
会返回一些Class<? extends T>
,但似乎只知道它会返回一些Class<? extends Foo>
。这是否意味着Foo是T的 erasure of the static type
答案 3 :(得分:-1)
非常好的问题。
java泛型的整个问题是它们在旧虚拟机中不存在。 Sun决定添加通用支持,而现有虚拟机不支持它们。
这可能是一个大问题,因为当他们发布新的Java时,旧的虚拟机将无法运行为最新版本的Java编写的任何代码,并且他们将无法继续支持旧虚拟机等。
因此,他们决定以可以在旧虚拟机中运行的方式实现泛型。 他们决定从BYTECODE中删除他们。
所以整个故事都是关于jvms的支持。
编辑:但这可能与这个问题无关。请参阅kutschkem的回答!另外,我的猜测是,在第二种情况下,演员表是Class<? extends List>
到Class<? extends T>
。
只有当List直接扩展T(因为它是隐式转换)时才能进行此转换。但另一方面,T扩展了List。
我认为与说法相同:
String s = "s";
Object o = s;
在上面的例子中,可以进行强制转换,因为String扩展了Object。对于反向,您需要一个显式的强制转换。
如果您向程序添加显式强制转换,则会编译:
public <T extends List> void doStuff2(T value) {
Class<? extends T> theClass = (Class<? extends T>) value.getClass();
}
答案 4 :(得分:-2)
guava reflection工具提供了一些工具来解决这个限制,并帮助您获得有界类型的类型(所以类)。
如本说明页第一行所述:
由于类型擦除,您无法传递泛型类对象 运行时 - 你可以抛出它们并假装它们是通用的, 但他们确实不是。
Guava提供TypeToken,它使用基于反射的技巧来允许 即使在运行时也可以操作和查询泛型类型。
想法是获得(或创建)有界类型的TypeToken,然后你可以询问它的类型。
我认为这个答案有点偏离主题,因为它没有直接回答问题('为什么'部分),但它可以修复不可编译的方法,它可以导致代码能够解决问题并给你解答为什么部分,并肯定会帮助其他人寻找'Java: - 如何-getClass()的有界类型“