Java、静态方法绑定和泛型都包含一些方法重载
Posted
技术标签:
【中文标题】Java、静态方法绑定和泛型都包含一些方法重载【英文标题】:Java, Static Method Binding and Generics all rolled up with some Method Overloading 【发布时间】:2011-10-02 09:16:53 【问题描述】:所以正如标题所暗示的那样,我的问题有点奇怪和复杂。我知道我将要做的事情打破了“良好”编程实践的所有规则,但是嘿,如果我们不活得一点点,生活会怎样?
所以我所做的是创建以下程序。 (请注意,这是真正尝试理解泛型的大型实验的一部分,因此某些函数名称可能有点乱序)
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 注释来验证
将 class Cage 中的
void printList(List<?>)
方法更改为非静态会产生相应的编译时错误
将 class BigCage 中的方法 void <U extends Dog> printList(List<U>)
更改为 void <U> printList(List<U>)
会产生相应的错误。
在 main() 中通过 class BigCage 调用 printList()(即 BigCage.printList(...))生成相同的运行时错误
在 main() 中,通过 class Cage 调用 printList()(即 Cage.printList(...))按预期工作只调用Cage中的printList版本
如果我将 printList(List<?>)
的定义从 class Cage 复制到 class BigCage,这将隐藏 class Cage 中的定义,我得到相应的编译器错误
现在,如果我不得不在黑暗中试一试这里发生了什么,我会说编译器搞砸了,因为它在多个阶段工作:类型检查和重载方法解析。在类型检查阶段,我们通过了违规行,因为 class BigCage 从 class Cage
继承了 void printList(List<?>)
,这将匹配我们扔给它的任何旧 List,因此请确保我们有一个可以工作的方法。但是,一旦需要使用实际调用的方法来解决问题,我们就会因为类型擦除而出现问题,这会导致 BigCage.printList
和 Cage.printList
具有完全相同的签名。这意味着当编译器正在寻找 animalCage.printList(animalCage);
的匹配项时,它将选择它匹配的第一个方法(如果我们假设它从 BigCage 的底部开始并将其原因直到 Object),它会首先找到 void <U extends Dog> printList(List<U>)
正确匹配的void printList(List<?>)
现在是我真正的问题:我离真相有多近?这是一个已知的错误?这是一个错误吗?我知道如何解决这个问题,这更像是一个学术问题。
**编辑**
正如下面很少有人发布的那样,此代码将在 Eclipse 中运行。 我的具体问题涉及 javac 版本 1.6.0_26。还有,我不是 确定在这种情况下我是否完全同意 Eclipse,即使它 有效,因为将
printList(List<?>)
添加到 BigCage 将 导致 Eclipse 中出现编译时错误,我看不出原因 当手动继承相同的方法时,它应该可以工作 添加(参见上面的注 6)。
【问题讨论】:
IANALL,但上面的注释 3 似乎是一把确凿的证据 - 它看起来像一个错误。另一方面,这些东西总是很乱——C++模板也有同样的问题——只是更多。 我在 Eclipse 中复制了相同的代码并运行了它。它工作得很好。没有例外。我已经复制了同一个文件中的所有类(我知道这是一种不好的做法,但我只是在检查你的问题)。 @Logan 我已经编辑了原始问题,以更好地反映您对 Eclipse 与 javac 的观点 【参考方案1】:考虑这个小问题:
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()
没有被重新编译。
从这个意义上说,静态方法之间存在一种压倒一切的关系。当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 中被允许。Java 7 禁止它,可能是出于技术以外的原因。
【讨论】:
因此,如果我遵循正确,编译器会将BigCage.printList(List<?>)
添加到 class BigCage 因为 class Cage 定义了 printList(List<?>)
并且 BigCage 扩展了 Cage。我想我唯一的后续问题是为什么我不能手动将printList(List<?>)
添加到 class BigCage 而没有编译时错误(注 6)?编译器是否不可能看到 BigCage 中的静态方法之一具有与基类之一相同的类型擦除并生成错误?
2 个类中的两个分离方法。不能把它们归为一类。请参阅***.com/questions/5527235/… javac6 无法在一个类中填充两个具有相同字节码签名的方法
如果我仍然遗漏了一些明显的东西,我深表歉意。如果我将 BigCage 中的方法 void <U extends Dog> printList(List<U>)
更改为 void <U> printList(List<U>)
(注 3),我仍然不明白为什么会出现编译器错误。 void <U extends Dog> printList(List<U>)
和 void <U extends Dog> printList(List<U>)
不会导致相同的擦除吗?当没有扩展时,为什么 U 扩展任何东西会导致错误消失?我知道 JVM 无法正确选择要选择的重载方法,但编译器不能检查这种情况并生成错误吗?
该错误是由于方法不明确引起的,与字节码无关。如果 U 以Dog
为界,则print(List<U>)
比print(List<?>)
更具体。如果有人调用print(list)
并且两种方法都匹配签名,则选择更具体的方法。如果U
是无限的,那么这两种方法都不比另一种更具体。如果调用匹配这两种方法,编译器会因为歧义而放弃。
这个讨论在java6下是正确的。这是即将过时的 Java 6 的一些有趣特性。在 java 7 中,甚至不允许同时声明这两个方法,无论 U 是否有界。你的程序不会在 java7 下编译。讨论一些只在 java 6 下才观察到的有趣的东西真的没有多大价值。【参考方案2】:
这不是错误。该方法是静态的。您不能覆盖静态方法,只能隐藏它们。
当您在 bigCage 上调用“printList
”时,您实际上是在 BigCage 类而不是对象上调用 printList
,它将始终调用您在 BigCage 类中声明的静态方法。
【讨论】:
我同意。没有任何东西被覆盖(如上所述)。然而,这些方法被过度使用。我看到的问题是调用animalCage.printList(animalCage)
应该是编译器错误,因为在 BigCage.printList(...) 中要求传递的 List 是 List<U extends Dog>
which animalCage 不是。 animalCage 的类型为List<Animal>
。这就是我们以 RunTime 异常结束的原因。 BigCage.printList 只需要 Dogs(或子类型),而传递的 animalCage 列表上有一只猫
你是对的。编译器假定(正确)当您调用 animalCage.printList(List) 时,您正在从超类调用 printList。
那为什么它实际上不调用 Cage.printList(...) 方法而不是调用 BigCage.printList(...) 中产生 RunTime 异常的版本呢?
您的代码正确编译并运行,没有任何类转换异常......它的价值。
您是使用 javac(1.6.0_26 版)还是 Eclipse 构建的?它们是两种不同的编译器。我在 Eclipse 中测试了相同的代码,发现它可以在那里工作。我想另一个问题是哪个编译器具有正确的行为?在这种情况下,我会选择 javac,因为如果我将 printList(List>) 的定义添加到 BigCage,Eclipse 会报告错误。见注 6。【参考方案3】:
这是此代码的最简单版本,但存在相同问题:
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());
我认为编译器应该返回错误:
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 编译器版本
BigCage#printList
BigCage: class Cat
BigCage: class Dog
恕我直言,这两个结果都不正确。
【讨论】:
【参考方案4】:恕我直言,此代码可能不正确。 BigCage 类中的方法 printList 应该会导致名称冲突,因为 Cage 中的 printList 具有相同的擦除,但都不会覆盖另一个。奇怪的是编译器编译它:)
生成的字节码(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();
或者也许源是好的,但是编译器正在创建错误的字节码?
事实上,我们使用参数BigCage<Animal> animalCage
调用方法<U extends Dog> void printList(List<U> list)
,而Animal 不扩展Dog。
【讨论】:
我同意编译器应该标记这一点,因为这两种方法将导致相同的擦除类型。有趣的是,如果您删除 U 在 BigCage 中扩展 Dog 的要求,它就会出现。请参阅注 3。无论哪种方式,上面的代码都破坏了泛型的类型安全。 是的。我认为在这种情况下,使用 BigCage 参数调用 BigCage#printList 是不正确的。但是删除内部方法可以消除问题。以上是关于Java、静态方法绑定和泛型都包含一些方法重载的主要内容,如果未能解决你的问题,请参考以下文章