因此,正如标题所示,我的问题有点奇怪和复杂。我知道我要做的事情打破了“良好”编程实践的所有规则,但是,嘿,如果我们过短的生活会怎样呢?

所以我要做的是创建以下程序。 (请注意,这是真正尝试理解泛型的大型实验的一部分,因此某些函数名称可能有点困惑)

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<?>)方法更改为非静态会生成适当的编译时错误
  • 类BigCage 中的void <U extends Dog> printList(List<U>)方法更改为void <U> printList(List<U>)会生成适当的错误。
  • main()中,通过类BigCage (即BigCage.printList(...))调用 printList()(即BigCage.printList(...))会生成相同的运行时错误
  • main()中通过类Cage (即Cage.printList(...))调用 printList()(如Cage.printList(...))可以按预期工作,仅在 Coji ojit中调用 printList 的版本
  • 如果我将printList(List<?>)的定义复制到类Cage 类BigCage 中,这会将定义隐藏在类Cage 中,则会得到适当的编译器错误

  • 现在,如果我不得不暗中了解这里发生的事情,那我想说编译器正在搞砸,因为它在多个阶段工作:类型检查和重载方法解析。在类型检查阶段,我们通过了冒犯性的一行,因为类BigCage void printList(List<?>)继承了class Cage,它将与我们抛出的所有旧列表匹配,因此请确保我们有一种可行的方法。但是,一旦有时间解决使用实际调用的方法时,由于类型擦除会导致问题,这会导致BigCage.printListCage.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禁止使用此技巧,可能是出于技术原因以外的其他原因。

    09-28 12:52