Java,静态方法绑定和泛型都汇总了一些方法重载

Bob*_*630 9 java generics overloading

因此,标题暗示我的问题有点奇怪和复杂.我知道我要做的事情打破了"好"编程实践的所有规则,但是嘿,如果我们不活一点,生活会怎样?

所以我做的是创建以下程序.(这不是一个更大的实验的一部分,真正尝试和理解泛型,所以一些功能名称可能有点乱序)

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
{
}
Run Code Online (Sandbox Code Playgroud)

现在令我困惑的是,这与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)
Run Code Online (Sandbox Code Playgroud)

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

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

现在,如果我不得不在黑暗中拍摄这里发生的事情,我会说编译器搞砸了,因为它在多个阶段工作:类型检查重载方法解析.在类型检查阶段,我们通过违规获得线因为类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<?>)

现在我的真实问题是:我在这里的真相有多接近?这是一个已知的错误?这是一个错误吗?我知道如何解决这个问题,这更像是一个学术问题.

**编辑**

由于下面发布的人很少,这段代码将在Eclipse中运行.我的具体问题涉及javac版本1.6.0_26.此外,我不确定在这种情况下我是否完全同意Eclipse,即使它有效,因为printList(List<?>)BigCage中添加一个将导致Eclipse中的编译时错误,我无法理解为什么它应该在相同时工作方法是手工添加的继承(参见上面的注释6).

irr*_*ble 8

考虑一下这个微不足道的问题:

class A
{
    static void foo(){ }
}
class B extends A
{
    static void foo(){ }
}
void test()
{
    A.foo();
    B.foo();
}
Run Code Online (Sandbox Code Playgroud)

假设我们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()字节码,无论是否Bfoo()今天.

在您的示例中,当编译器看到时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).另请参见Java中的类型擦除和重载:为什么这样做?这个技巧只允许在Java 6中使用.Java 7禁止它,可能是出于技术原因以外的原因.