加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > Java > 正文

Java,静态方法绑定和泛型都被卷入了一些方法重载

发布时间:2020-12-14 05:45:14 所属栏目:Java 来源:网络整理
导读:所以标题意味着我的问题有点奇怪和复杂.我知道我要做什么打破所有的“好”编程实践的规则,但是嘿,如果我们不生活有什么生活? 所以我做的是创建以下程序. (这不是一个更大的实验的一部分,真正尝试和理解泛型,所以一些功能名称可能有点不合格) import java.ut
所以标题意味着我的问题有点奇怪和复杂.我知道我要做什么打破所有的“好”编程实践的规则,但是嘿,如果我们不生活有什么生活?

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

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<?>)方法更改为非静态会生成适当的编译时错误
>更改方法void< U扩展Dog>类BigCage中的printList(List< U>)为void< U> printList(List< U>)生成适当的错误.
>在main()中通过BigCage类(即BigCage.printList(…))调用printList())生成相同的运行时错误
>在main()中通过Cage(即Cage.printList(…))调用printList()的工作原理只有在Cage中调用printList的版本
>如果我将printList(List<?>)的定义从Cage类复制到BigCage类,这将隐藏Cage类中的定义,我得到了相应的编译器错误

现在如果我不得不在黑暗中拍摄一下这里发生了什么,我会说编译器正在拧紧,因为它在多个阶段工作:类型检查和重载方法解析.在类型检查期间,我们通过违规行,因为BigCage类继承了Cage类中的void printList(List<?>),它将匹配我们抛出的任何旧列表,所以确保我们有一个可以工作的方法.但是,一旦遇到实际调用的方法来解决问题,我们就会遇到类型擦除问题,这会导致BigCage.printList和Cage.printList都具有完全相同的签名.这意味着当编译器正在寻找一个匹配的animalCage.printList(animalCage);它将选择它匹配的第一种方法(如果我们假设它从底部开始使用BigCage,并且将它的原因作为Object),它会找到void< U extends Dog> printList(List< U>)而不是正确的匹配void printList(List<?>)

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

**EDIT**

As few people have posted below,this code will work in Eclipse.
My specific question deals with javac version 1.6.0_26. Also,I’m not
sure if I completely agree with Eclipse in this case,even though it
works,because adding a printList(List<?>) to BigCage will
result in a compile time error in Eclipse and I can’t see reason why
it should work when the same method is inherited verses manually
added (See Note 6 above).

解决方法

考虑这个微不足道的问题:
class A
{
    static void foo(){ }
}
class B extends A
{
    static void foo(){ }
}
void test()
{
    A.foo();
    B.foo();
}

假设我们从B中删除了foo方法,我们只重新编译B本身,当我们运行test()时会发生什么?是否会发现链接错误,因为没有找到B.foo()?

根据JLS3#13.4.12,删除B.foo不会破坏二进制兼容性,因为A.foo仍然被定义.这意味着,当执行B.foo()时,调用A.foo().记住,没有重新编译test(),所以这个转发必须由JVM处理.

相反,我们从B中删除foo方法,然后重新编译全部.即使编译器静态地知道B.foo()实际上意味着A.foo(),它仍然在字节码中生成B.foo().现在,JVM将B.foo()转发到A.foo().但是,如果将来B获得新的foo方法,即使没有重新编译test(),新的方法将在运行时被调用.

在这个意义上,静态方法之间有一个压倒一切的关系.当编译看到B.foo()时,它必须将它编译为B.foo()in by代码,无论B是否有一个foo()今天.

在您的示例中,当编译器看到BigCage.printList(animalCage)时,它正确地推断出它实际上是调用Cage.printList(List<?>).因此,需要将该调用编译为BigCage.printList(List<?>)的字节码 – 目标类必须是BigCage而不是Cage.

哎呀!字节码格式尚未升级以处理方法签名.泛型信息以字节码保存为辅助信息,但是对于方法调用,它是旧的方式.

擦除发生.该调用实际上编译成BigCage.printList(List).太糟糕了,BigCage在擦除后也有一个printList(List).在运行时,该方法被调用!

这个问题是由于Java规范和JVM规范之间的不匹配造成的.

Java 7收紧了一点;实现字节码和JVM无法处理这种情况,它不再编译您的代码:

error: name clash:
printList(List) in BigCage and
printList(List) in Cage have the
same erasure,yet neither hides the
other

另一个有趣的事实:如果两种方法有不同的返回类型,你的程序将正常工作.这是因为在字节码中,方法签名包括返回类型.所以Dog printList(List)和Object printList(List)之间没有混淆.另请参见Type Erasure and Overloading in Java: Why does this work?这个技巧只允许在Java 6中使用.Java 7禁止它,可能是由于技术之外的原因.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读