Java,静态方法绑定和泛型都伴随着一些方法重载 - java

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

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

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()生成相同的运行时错误
  • 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的底部开始并按原因进行操作,那么它将首先找到void <U extends Dog> printList(List<U>)而不是正确的匹配项void printList(List<?>)
    现在是我真正的问题:我在这里与事实有多接近?这是一个已知的错误?这是一个bug吗?我知道如何解决这个问题,这更多是一个学术问题。

    **编辑**

    很少有人在下面发布这些代码,它们将在Eclipse中运行。
    我的特定问题与javac版本1.6.0_26有关。而且我不是
    确保在这种情况下我是否完全同意Eclipse,即使它
    有效,因为将printList(List<?>)添加到 BigCage 将会
    在Eclipse中导致编译时错误,我看不到原因
    当手动继承相同的方法时,它应该工作
    已添加(请参见上面的注6 )。

    参考方案

    考虑一下这个琐碎的问题:

    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无法处理这种情况,它不再编译您的代码:

    错误:名称冲突:
    BigCage中的printList(List)和
    Cage中的printList(List)具有
    同样的擦除,但都没有隐藏
    其他

    另一个有趣的事实:如果两种方法的返回类型不同,则您的程序将正确运行。这是因为在字节码中,方法签名包括返回类型。因此,Dog printList(List)Object printList(List)之间没有混淆。另请参见Type Erasure and Overloading in Java: Why does this work?。仅在Java 6中允许使用此技巧。Java7禁止使用此技巧,可能出于技术原因之外的其他原因。

    Java中的<<或>>>是什么意思? - java

    This question already has answers here: Closed 7 years ago. Possible Duplicate: What does >> and >>> mean in Java?我在一些Java代码中遇到了一些陌生的符号,尽管代码可以正确编译和运行,但对于括号在此代码中的作用却感…

    List <Dog>是List <Animal>的子类吗?为什么Java泛型不是隐式多态的? - java

    我对Java泛型如何处理继承/多态感到困惑。假设以下层次结构-动物(父母)狗-猫(儿童)因此,假设我有一个方法doSomething(List<Animal> animals)。根据继承和多态性的所有规则,我假设List<Dog>是List<Animal>,而List<Cat>是List<Animal&g…

    菱形运算符<>是否等于<?> - java

    我在util.TreeSet类中发现,其中一个构造函数正在使用具有空泛型类型的新TreeMap调用另一个构造函数。 public TreeSet(Comparator<? super E> comparator) { this(new TreeMap<>(comparator)); } new TreeMap<>是什么意思…

    休眠映射<键,设置<值>> - java

    我有以下表格:@Entity @Table(name = "events") Event --id --name @Entity @Table(name = "state") State --id --name @Entity @Table(name = "action") Action --id …

    无法从ArrayList <String>转换为List <Comparable> - java

    当我写下面的代码时,编译器说 无法从ArrayList<String>转换为List<Comparable>private List<Comparable> get(){ return new ArrayList<String>(); } 但是当我用通配符编写返回类型时,代码会编译。private List&l…