gpt4 book ai didi

Java、静态方法绑定(bind)和泛型都包含一些方法重载

转载 作者:塔克拉玛干 更新时间:2023-11-03 03:14:34 25 4
gpt4 key购买 nike

正如标题所暗示的那样,我的问题有点奇怪和复杂。我知道我将要做的事情违反了“良好”编程实践的所有规则,但是嘿,如果我们不活一点,生活会怎样?

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

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. 更改 void printList(List<?>) class Cage 中的非静态方法会生成相应的编译时错误
  3. 改变方法 void <U extends Dog> printList(List<U>)BigCage 类void <U> printList(List<U>)生成适当的错误。
  4. ma​​in() 中通过 class BigCage 调用 printList()(即 BigCage.printList(...))生成相同的运行时错误
  5. ma​​in() 中通过 class Cage 调用 printList()(即 Cage.printList(...))按预期工作只调用Cage
  6. 中的 printList版本
  7. 如果我复制 printList(List<?>) 的定义从 class Cageclass BigCage,这将隐藏 class Cage 中的定义,我得到相应的编译器错误

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

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

**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();
}

假设我们删除 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 无法处理这种情况,它不再编译你的代码:

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 禁止它,可能是出于技术原因以外的原因。

关于Java、静态方法绑定(bind)和泛型都包含一些方法重载,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6643648/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com