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) 

这里需要注意的一些事项:

  1. 两个printList 不会覆盖,但会按预期相互重载(它们具有不同的类型,因为它们的参数的generics类型不同)。 这可以使用@Override注释进行validation
  2. 类Cage中void printList(List)方法更改为非静态会生成适当的编译时错误
  3. 更改方法void printList(List)类BigCage中将 void printList(List) 扩展void printList(List)生成相应的错误。
  4. main()中通过类BigCage调用printList() (即BigCage.printList(…))生成相同的运行时错误
  5. main()中通过类Cage调用printList() (即Cage.printList(…))按预期工作只调用Cage中printList版本
  6. 如果我将printList(List)的定义从类Cage复制到类BigCage ,这将隐藏类Cage中的定义,我得到相应的编译器错误

现在,如果我不得不在黑暗中拍摄这里发生的事情,我会说编译器搞砸了,因为它在多个阶段工作: 类型检查重载方法分辨率 。 在类型检查阶段,我们通过违规行,因为类BigCageclass Cageinheritance了void printList(List) ,它将匹配我们抛出的任何旧List,所以我们确定我们有一个可行的方法。 然而,一旦用方法来解析实际调用,我们就会遇到问题,因为类型擦除导致BigCage.printListCage.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 animalCage调用方法 void printList(List list) ,Animal 扩展Dog。