因为标题暗示我的问题有点奇怪和复杂。我知道我要做的事情打破了“好”编程实践的所有规则,但嘿,如果我们不活一点,生活会怎样?
所以我做的是创建以下程序。 (这不是一个更大的实验的一部分,真正尝试和理解泛型,所以一些功能名称可能有点乱序)
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 ,这将隐藏 class Cage 中的定义,我得到了相应的编译错误现在,如果我不得不在黑暗中拍摄这里发生的事情,我会说编译器搞砸了,因为它在多个阶段工作:类型检查和重载方法解决方案。在类型检查阶段,我们通过违规行,因为类BigCage 从void printList(List<?>)
继承了class Cage
,它将匹配我们抛出的任何旧List,所以我们确定我们有一个方法那可行。但是,一旦用实际调用方法解决,我们就会遇到问题,因为类型擦除导致BigCage.printList
和Cage.printList
具有完全相同的签名。这意味着当编译器正在寻找animalCage.printList(animalCage);
的匹配时,它将选择它匹配的第一个方法(如果我们假设它从BigCage的底部开始,并将其原因用于对象),它将找到{{1首先代替正确的匹配void <U extends Dog> printList(List<U>)
现在提出我真正的问题:我在这里的真相有多接近?这是一个已知的错误?这是一个错误吗?我知道如何解决这个问题,这更像是一个学术问题。
**编辑**
很少有人发布在下面,这段代码可以在Eclipse中使用。 我的具体问题涉及javac版本1.6.0_26。而且,我不是 确定如果我在这种情况下完全同意Eclipse,即使它 可行,因为将
void printList(List<?>)
添加到 BigCage 会 导致Eclipse中的编译时错误,我看不出原因 当手动继承相同的方法时,它应该工作 添加(参见上面的注释6 )。
答案 0 :(得分:8)
考虑这个微不足道的问题:
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()
,也会在运行时调用新方法。
从这个意义上说,静态方法之间存在着一种压倒一切的关系。当编译看到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无法处理这种情况,它不再编译你的代码:
错误:名称冲突: bigCage和中的printList(List) 在Cage中的printList(List)有 相同的擦除,但都没有隐藏 其他
另一个有趣的事实:如果这两种方法具有不同的返回类型,那么您的程序将正常工作。这是因为在字节码中,方法签名包括返回类型。因此Dog printList(List)
和Object printList(List)
之间没有混淆。另请参阅Type Erasure and Overloading in Java: Why does this work?此技巧仅在Java 6中允许.Java 7禁止使用它,可能是出于技术原因以外的原因。
答案 1 :(得分:2)
这不是错误。该方法是静态的。您不能覆盖静态方法,只能隐藏它们。
当你在bigCage上调用“printList
”时,你真的在BigCage类上调用printList
而不是对象,它总是调用在BigCage类中声明的静态方法。
答案 2 :(得分:2)
这是此代码的最简单版本,具有相同的问题:
import java.util.*;
public class GenericTestsClean {
public static void main(String[] args) {
List<Animal> animalCage = new ArrayList<Animal>();
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 <U extends Dog> void printList(List<U> 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: <U extends Dog>printList(java.util.List<U>) in BigCage cannot be applied to (java.util.List<Animal>)
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
恕我直言,这个结果都是不正确的。
答案 3 :(得分:1)
恕我直言,此代码可能不正确。 类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();
}
}
强制转换循环会导致异常。 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();
}
}
或者源代码可以,但编译器正在创建错误的字节码?
事实是,我们将方法<U extends Dog> void printList(List<U> list)
称为参数BigCage<Animal> animalCage
,而动物不会扩展狗。