所以我做的是创建以下程序. (这不是一个更大的实验的一部分,真正尝试和理解泛型,所以一些功能名称可能有点不合格)
import java.util.*; public class GenericTestsClean { public static void test2() { BigCage<Animal> animalCage=new BigCage<Animal>(); BigCage<Dog> dogCage=new BigCage<Dog>(); 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<T> extends Cage<T> { public static <U extends Dog> void printList(List<U> list) { System.out.println("*************"+list.getClass().toString()); for(Object obj : list) System.out.println("BigCage: "+obj.getClass().toString()); } } class Cage<T> extends ArrayList<T> { 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进行编译,但是当我运行它时,会得到以下类转换异常:
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不会被覆盖,但是按预期的方式重载(他们有不同的类型,因为它们的参数的泛型类型不同).这可以通过使用@Override注释进行验证
>将Cage类中的void printList(List<?>)方法更改为非静态会生成适当的编译时错误
>更改方法void< U扩展Dog>类BigCage中的printList(List< U>)为void< U> printList(List< U>)生成适当的错误.
>在main()中通过BigCage类(即BigCage.printList(…))调用printList())生成相同的运行时错误
>在main()中通过Cage(即Cage.printList(…))调用printList()的工作原理只有在Cage中调用printList的版本
>如果我将printList(List<?>)的定义从Cage类复制到BigCage类,这将隐藏Cage类中的定义,我得到了相应的编译器错误
现在如果我不得不在黑暗中拍摄一下这里发生了什么,我会说编译器正在拧紧,因为它在多个阶段工作:类型检查和重载方法解析.在类型检查期间,我们通过违规行,因为BigCage类继承了Cage类中的void printList(List<?>),它将匹配我们抛出的任何旧列表,所以确保我们有一个可以工作的方法.但是,一旦遇到实际调用的方法来解决问题,我们就会遇到类型擦除问题,这会导致BigCage.printList和Cage.printList都具有完全相同的签名.这意味着当编译器正在寻找一个匹配的animalCage.printList(animalCage);它将选择它匹配的第一种方法(如果我们假设它从底部开始使用BigCage,并且将它的原因作为Object),它会找到void< U extends Dog> printList(List< U>)而不是正确的匹配void printList(List<?>)
现在我真正的问题:我在这里接近真相?这是一个已知的错误吗?这是一个bug吗?我知道如何解决这个问题,这更像是一个学术问题.
**EDIT**
As few people have posted below,this code will work in Eclipse.
My specific question deals with javac version 1.6.0_26. Also,I’m not
sure if I completely agree with Eclipse in this case,even though it
works,because adding aprintList(List<?>)
to BigCage will
result in a compile time error in Eclipse and I can’t see reason why
it should work when the same method is inherited verses manually
added (See Note 6 above).
解决方法
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仍然被定义.这意味着,当执行B.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()in by代码,无论B是否有一个foo()今天.
在您的示例中,当编译器看到BigCage.printList(animalCage)时,它正确地推断出它实际上是调用Cage.printList(List<?>).因此,需要将该调用编译为BigCage.printList(List<?>)的字节码 – 目标类必须是BigCage而不是Cage.
哎呀!字节码格式尚未升级以处理方法签名.泛型信息以字节码保存为辅助信息,但是对于方法调用,它是旧的方式.
擦除发生.该调用实际上编译成BigCage.printList(List).太糟糕了,BigCage在擦除后也有一个printList(List).在运行时,该方法被调用!
这个问题是由于Java规范和JVM规范之间的不匹配造成的.
Java 7收紧了一点;实现字节码和JVM无法处理这种情况,它不再编译您的代码:
error: name clash:
printList(List) in BigCage and
printList(List) in Cage have the
same erasure,yet neither hides the
other
另一个有趣的事实:如果两种方法有不同的返回类型,你的程序将正常工作.这是因为在字节码中,方法签名包括返回类型.所以Dog printList(List)和Object printList(List)之间没有混淆.另请参见Type Erasure and Overloading in Java: Why does this work?这个技巧只允许在Java 6中使用.Java 7禁止它,可能是由于技术之外的原因.