Java,静态方法绑定和generics都汇总了一些方法重载
所以标题暗示我的问题有点奇怪和复杂。 我知道我要做的事情打破了“好”编程实践的所有规则,但是嘿,如果我们不活一点,生活会怎样?
所以我做的是创建以下程序。 (这不是一个更大的实验的一部分,真正尝试和理解generics,所以一些function名称可能有点乱序)
import java.util.*; public class GenericTestsClean { public static void test2() { BigCage animalCage=new BigCage(); BigCage dogCage=new BigCage(); dogCage.add(new Dog()); animalCage.add(new Cat()); animalCage.add(new Dog()); animalCage.printList(dogCage); animalCage.printList(animalCage); } public static void main(String [] args) { //What will this print System.out.println("\nTest 2"); test2(); } } class BigCage extends Cage { public static void printList(List list) { System.out.println("*************"+list.getClass().toString()); for(Object obj : list) System.out.println("BigCage: "+obj.getClass().toString()); } } class Cage extends ArrayList { public static void printList(List list) { System.out.println("*************"+list.getClass().toString()); for(Object obj : list) System.out.println("Cage: "+obj.getClass().toString()); } } class Animal { } class Dog extends Animal { } class Cat extends Animal { }
现在令我困惑的是,这与javac 1.6.0_26编译良好但是当我运行它时,我得到以下类转换exception:
Test 2 *************class BigCage BigCage: class Dog *************class BigCage Exception in thread "main" java.lang.ClassCastException: Cat cannot be cast to Dog at BigCage.printList(GenericTestsClean.java:31) at GenericTestsClean.test2(GenericTestsClean.java:13) at GenericTestsClean.main(GenericTestsClean.java:21)
这里需要注意的一些事项:
- 两个printList 不会覆盖,但会按预期相互重载(它们具有不同的类型,因为它们的参数的generics类型不同)。 这可以使用@Override注释进行validation
- 将类Cage中的
void printList(List)
方法更改为非静态会生成适当的编译时错误 - 更改方法
void printList(List)
在类BigCage中将void printList(List)
扩展为void printList(List)
生成相应的错误。 - 在main()中通过类BigCage调用printList() (即BigCage.printList(…))生成相同的运行时错误
- 在main()中通过类Cage调用printList() (即Cage.printList(…))按预期工作只调用Cage中的printList版本
- 如果我将
printList(List)
的定义从类Cage复制到类BigCage ,这将隐藏类Cage中的定义,我得到相应的编译器错误
现在,如果我不得不在黑暗中拍摄这里发生的事情,我会说编译器搞砸了,因为它在多个阶段工作: 类型检查和重载方法分辨率 。 在类型检查阶段,我们通过违规行,因为类BigCage从class Cage
inheritance了void printList(List)
,它将匹配我们抛出的任何旧List,所以我们确定我们有一个可行的方法。 然而,一旦用方法来解析实际调用,我们就会遇到问题,因为类型擦除导致BigCage.printList
和Cage.printList
具有完全相同的签名。 这意味着编译器在寻找animalCage.printList(animalCage);
的匹配animalCage.printList(animalCage);
它将选择它匹配的第一个方法(如果我们假设它从BigCage的底部开始并将其工作原理直到Object)它将首先找到void printList(List)
而不是正确的match void printList(List)
现在我的真实问题是 :我在这里的真相有多接近? 这是一个已知的错误? 这是一个错误吗? 我知道如何解决这个问题,这更像是一个学术问题。
**编辑**
由于下面发布的人数很少,因此该代码可以在Eclipse中使用。 我的具体问题涉及javac版本1.6.0_26。 另外,我不确定在这种情况下我是否完全同意Eclipse,即使它有效,因为向BigCage添加
printList(List)
将导致Eclipse中的编译时错误,我无法看到原因当手动添加相同的方法时,为什么它应该工作(参见上面的注释6 )。
考虑一下这个微不足道的问题:
class A { static void foo(){ } } class B extends A { static void foo(){ } } void test() { A.foo(); B.foo(); }
假设我们从B
删除了foo
方法,并且我们只重新编译B
本身,当我们运行test()
时会发生什么? 它应该抛出链接错误,因为B.foo()
吗?
根据JLS3#13.4.12,删除B.foo
不会破坏二进制兼容性,因为仍然定义了A.foo
。 这意味着,当执行A.foo()
将调用A.foo()
。 请记住,没有重新编译test()
,因此这种转发必须由JVM处理。
相反,让我们从B
删除foo
方法,并重新编译所有。 即使编译器静态地知道B.foo()
实际上意味着A.foo()
,它仍然在字节码中生成B.foo()
。 目前,JVM会将B.foo()
转发给A.foo()
。 但是如果将来B
获得一个新的foo
方法,即使不重新编译test()
,也会在运行时调用新方法。
从这个意义上讲,静态方法之间存在着至关重要的关系。 当编译看到B.foo()
,它必须在字节码B.foo()
其编译为B.foo()
,无论B
今天是否有foo()
。
在您的示例中,当编译器看到BigCage.printList(animalCage)
,它正确地推断它实际上正在调用Cage.printList(List>)
。 因此需要将调用编译为字节码,如BigCage.printList(List>)
– 目标类必须是BigCage
,而不是Cage
。
哎呀! 字节码格式尚未升级为处理方法签名。 generics信息在字节码中保留为辅助信息,但对于方法调用,它是旧的方式。
擦除发生了。 该调用实际上被编译为BigCage.printList(List)
。 太糟糕了, BigCage
在擦除后还有一个printList(List)
。 在运行时,调用该方法!
此问题是由于Java规范和JVM规范之间的不匹配造成的。
Java 7收紧了一点; 实现字节码和JVM无法处理这种情况,它不再编译你的代码:
错误:名称冲突:BigCage中的printList(List)和Cage中的printList(List)具有相同的擦除,但都没有隐藏其他
另一个有趣的事实:如果这两种方法具有不同的返回类型,您的程序将正常工作。 这是因为在字节码中,方法签名包括返回类型。 所以Dog printList(List)
和Object printList(List)
之间没有混淆。 另请参见Java中的类型擦除和重载:为什么这样做? 这个技巧只能在Java 6中使用.Java 7禁止它,可能是出于技术原因以外的原因。
这不是一个错误。 该方法是静态的。 您无法覆盖静态方法,只能隐藏它们。
当您在bigCage上调用“ printList
”时,您实际上是在BigCage类上调用printList
而不是对象,它总是调用在BigCage类中声明的静态方法。
这是此代码的最简单版本,具有相同的问题:
import java.util.*; public class GenericTestsClean { public static void main(String[] args) { List animalCage = new ArrayList (); animalCage.add(new Cat()); animalCage.add(new Dog()); BigCage.printList(animalCage); } } class Animal {} class Dog extends Animal {} class Cat extends Animal {} class BigCage extends Cage { public static void printList(List list) { System.out.println("BigCage#printList"); for (Object obj : list) { System.out.println("BigCage: " + obj.getClass().toString()); } } } class Cage { public static void printList(List list) { System.out.println("Cage#printList"); for (Object obj : list) { System.out.println("Cage: " + obj.getClass().toString()); } } }
我认为compiller应该返回错误:
GenericTestsClean.java:8: printList(java.util.List) in BigCage cannot be applied to (java.util.List) BigCage.printList(animalCage); ^ 1 error
(或者说同一个错误的名字冲突),但事实并非如此。
在解散之后(javap -c GenericTestsClean)我们得到了:
invokestatic #9; //Method BigCage.printList:(Ljava/util/List;)V
调用java GenericTestsClean
:
javac 1.6.0_10版
BigCage#printList Exception in thread "main" java.lang.ClassCastException: Cat cannot be cast to Dog at BigCage.printList(GenericTestsClean.java:19) at GenericTestsClean.main(GenericTestsClean.java:8)
Eclipse compiller版本
BigCage#printList BigCage: class Cat BigCage: class Dog
恕我直言这个结果都是不正确的。
恕我直言这段代码可能不正确。 类BigCage中的方法printList应该导致Cage中的名称冲突coz printList具有相同的擦除,但是不会覆盖另一个。 奇怪的是compiller编译它:)
生成的字节码(javac 1.6.0_10)等效于:
class BigCage extends Cage { public static void printList(List list){ System.out.println((new StringBuilder()).append("*************").append(list.getClass().toString()).toString()); Dog dog; for(Iterator iterator = list.iterator(); iterator.hasNext(); System.out.println((new StringBuilder()).append("BigCage: ").append(dog.getClass().toString()).toString())) dog = (Dog)iterator.next(); } }
cast in循环导致exception 。 Eclipse内置编译器生成这样的代码(无一例外地工作):
class BigCage extends Cage{ public static void printList(List list){ System.out.println((new StringBuilder("*************")).append(list.getClass().toString()).toString()); Object obj; for(Iterator iterator = list.iterator(); iterator.hasNext(); System.out.println((new StringBuilder("BigCage: ")).append(obj.getClass().toString()).toString())) obj = iterator.next(); } }
或者源可以,但编译器正在创建错误的字节码? 事实是我们用参数BigCage
调用方法 void printList(List list)
,Animal 不扩展Dog。