因此,正如标题所示,我的问题有点奇怪和复杂。我知道我要做的事情打破了“良好”编程实践的所有规则,但是,嘿,如果我们过短的生活会怎样呢?
所以我要做的是创建以下程序。 (请注意,这是真正尝试理解泛型的大型实验的一部分,因此某些函数名称可能有点困惑)
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)
这里需要注意的一些事情:
void printList(List<?>)
方法更改为非静态会生成适当的编译时错误void <U extends Dog> printList(List<U>)
方法更改为void <U> printList(List<U>)
会生成适当的错误。 printList(List<?>)
的定义复制到类Cage 的类BigCage 中,这会将定义隐藏在类Cage 中,则会得到适当的编译器错误现在,如果我不得不暗中了解这里发生的事情,那我想说编译器正在搞砸,因为它在多个阶段工作:类型检查和重载方法解析。在类型检查阶段,我们通过了冒犯性的一行,因为类BigCage 从
void printList(List<?>)
继承了class Cage
,它将与我们抛出的所有旧列表匹配,因此请确保我们有一种可行的方法。但是,一旦有时间解决使用实际调用的方法时,由于类型擦除会导致问题,这会导致BigCage.printList
和Cage.printList
具有完全相同的签名。这意味着,当编译器正在寻找animalCage.printList(animalCage);
的匹配项时,它将选择它匹配的第一个方法(如果我们假设它从BigCage的底部开始并按其原因运行到Object),它将首先找到void <U extends Dog> printList(List<U>)
而不是正确的匹配项void printList(List<?>)
现在是我真正的问题:我在这里与事实有多接近?这是一个已知的错误?这根本是个错误吗?我知道如何解决这个问题,这更多是一个学术问题。
最佳答案
考虑一下这个琐碎的问题:
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()
转发到A.foo()
。但是,如果将来B
获得新的foo
方法,即使不重新编译test()
,新方法也会在运行时被调用。从这个意义上说,静态方法之间存在着最重要的关系。当compile看到
B.foo()
时,它必须将其编译为字节码形式的B.foo()
,无论今天B
是否具有foo()
。在您的示例中,当编译器看到
BigCage.printList(animalCage)
时,它可以正确推断出它实际上是在调用Cage.printList(List<?>)
。因此,它需要将调用编译为BigCage.printList(List<?>)
的字节码-目标类在这里必须是BigCage
而不是Cage
。糟糕!字节码格式尚未升级为可处理此类方法签名。泛型信息作为辅助信息保留在字节码中,但是对于方法调用而言,这是旧方法。
发生擦除。该调用实际上已编译为
BigCage.printList(List)
。太糟糕的BigCage
在擦除后也会有一个printList(List)
。在运行时,将调用该方法!此问题是由于Java规范和JVM规范之间的不匹配所致。
Java 7稍微加强了一点;意识到字节码和JVM无法处理这种情况,它不再编译您的代码:
另一个有趣的事实:如果两种方法的返回类型不同,则您的程序将正确运行。这是因为在字节码中,方法签名包括返回类型。因此,
Dog printList(List)
和Object printList(List)
之间没有混淆。另请参见Type Erasure and Overloading in Java: Why does this work?。仅在Java 6中允许使用此技巧。Java7禁止使用此技巧,可能是出于技术原因以外的其他原因。