考虑这个小问题:
class A
{
static void foo(){ }
}
class B extends A
{
static void foo(){ }
}
void test()
{
A.foo();
B.foo();
}
假设我们删除foo
方法来自B
,我们只需重新编译B
本身,当我们跑步时会发生什么test()
?它是否应该抛出链接错误,因为B.foo()
没有找到吗?
根据 JLS3 #13.4.12,删除B.foo
不会破坏二进制兼容性,因为A.foo
仍然被定义。这意味着,当B.foo()
被执行,A.foo()
被调用。请记住,没有重新编译test()
,所以这个转发必须由JVM来处理。
相反,我们删除foo
方法来自B
,并重新编译全部。即使编译器静态地知道B.foo()
实际上意味着A.foo()
,它仍然生成B.foo()
在字节码中。目前,JVM 将转发B.foo()
to A.foo()
。但如果将来B
获得新的foo
方法,新方法将在运行时被调用,即使test()
没有重新编译。
从这个意义上说,静态方法之间存在着压倒一切的关系。当编译看到B.foo()
,它必须将其编译为B.foo()
在字节码中,无论是否B
has a foo()
today.
在你的例子中,当编译器看到BigCage.printList(animalCage)
,它正确地推断出它实际上正在调用Cage.printList(List<?>)
。所以它需要将调用编译为字节码:BigCage.printList(List<?>)
- 目标类别必须是BigCage
在这里而不是Cage
.
哎呀!字节码格式尚未升级以处理此类方法签名。泛型信息作为辅助信息保留在字节码中,但对于方法调用,这是旧的方式。
擦除发生。该调用实际上被编译成BigCage.printList(List)
。太糟糕了BigCage
还有一个printList(List)
擦除后。在运行时,该方法被调用!
这个问题是由于Java规范和JVM规范不匹配造成的。
Java 7 稍微收紧了;意识到字节码和 JVM 无法处理这种情况,它不再编译你的代码:
错误:名称冲突:
BigCage 中的 printList(List) 和
Cage 中的 printList(List) 有
同样的擦除,但都没有隐藏
其他
另一个有趣的事实:如果这两个方法具有不同的返回类型,您的程序将正常工作。这是因为在字节码中,方法签名包括返回类型。所以两者之间不存在混淆Dog printList(List)
and Object printList(List)
。也可以看看Java 中的类型擦除和重载:为什么会起作用? https://stackoverflow.com/questions/5527235/type-erasure-and-overloading-in-java-why-does-this-work/5528802#5528802这个技巧只在 Java 6 中允许。Java 7 禁止它,可能是出于技术原因以外的原因。