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

时间:2011-07-10 21:02:20

标签: 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
{
}

现在让我感到困惑的是,使用 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)

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

  1. 两个printList是 NOT 覆盖,但是按预期相互重载(它们具有不同的类型,因为它们的参数的泛型类型不同)。这可以使用@Override注释
  2. 进行验证
  3. 类Cage 中的void printList(List<?>)方法更改为非静态会产生适当的编译时错误
  4. 类BigCage 中的方法void <U extends Dog> printList(List<U>)更改为void <U> printList(List<U>)会产生相应的错误。
  5. main()中通过类BigCage (即BigCage.printList(...))调用 printList()生成相同的内容运行时错误
  6. main()中通过类Cage 调用 printList()(即Cage.printList(...))按预期工作仅在 Cage
  7. 中调用 printList 的版本
  8. 如果我将printList(List<?>)的定义从类Cage 复制到类BigCage ,这将隐藏 class Cage 中的定义,我得到了相应的编译错误
  9. 现在,如果我不得不在黑暗中拍摄这里发生的事情,我会说编译器搞砸了,因为它在多个阶段工作:类型检查重载方法解决方案。在类型检查阶段,我们通过违规行,因为类BigCage void printList(List<?>)继承了class Cage,它将匹配我们抛出的任何旧List,所以我们确定我们有一个方法那可行。但是,一旦用实际调用方法解决,我们就会遇到问题,因为类型擦除导致BigCage.printListCage.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 )。

4 个答案:

答案 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,而动物不会扩展狗。