Java、静态方法绑定和泛型都与一些方法重载结合在一起

发布于 2024-11-19 06:05:48 字数 3862 浏览 1 评论 0原文

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

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

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

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

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

**编辑**

正如下面很少有人发布的那样,这段代码可以在 Eclipse 中运行。 我的具体问题涉及 javac 版本 1.6.0_26。还有,我不是 确定在这种情况下我是否完全同意 Eclipse,即使它 有效,因为将 printList(List) 添加到 BigCage 将 导致 Eclipse 中出现编译时错误,我不明白原因 当相同的方法被继承而不是手动时它应该起作用 添加(请参阅上面的注释 6)。

So as the title implies my question is a bit odd and complicated. I know what I'm about to do breaks all the rules of "good" programming practices but hey, what's life if we don't live a little?

So what I did was create the following program. (Note this was part of a larger experiment to really try and understand generics so some of the function names maybe a bit out of order)

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
{
}

Now what is confusing me is that this compiles fine with javac 1.6.0_26 but when I run it I get the following class cast exception:

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)

A number of things to note here:

  1. The two printList are NOT overriding but overloading each other as expected(They have different types because the generic types of their arguments are different). This can be verified by using the @Override annotation
  2. Changing the void printList(List<?>) method in class Cage to be non-static generates an appropriate compile time error
  3. Changing the method void <U extends Dog> printList(List<U>) in class BigCage to void <U> printList(List<U>) generates an appropriate error.
  4. In main() calling printList() through the class BigCage (ie BigCage.printList(...)) generates the same runtime error
  5. In main() calling printList() through the class Cage (ie Cage.printList(...)) works as expected only calling the version of printList in Cage
  6. If I copy the definition for printList(List<?>) to class BigCage from class Cage, which will hide the definition in class Cage, I get the appropriate compiler error

Now if I had to take a shot in the dark as to what is going on here, I'd say the compiler is screwing up because it's working in multiple phases: Type Checking and Overloaded Method Resolution. During the type checking phase we get through the offending line because class BigCage inherited void printList(List<?>) from class Cage which will match any old List we throw at it, so sure we have a method that will work. However once it comes time to resolve with method to actually call we have a problem due to Type Erasure which causes both BigCage.printList and Cage.printList to have the exact same signature. This means when compiler is looking for a match for animalCage.printList(animalCage); it will choose the first method it matches (and if we assume it starts at the bottom with BigCage and works its why up to Object) it'll find void <U extends Dog> printList(List<U>) first instead of the correct match void printList(List<?>)

Now for my real question: How close to the truth am I here? Is this a known bug? Is this a bug at all? I know how to get around this problem, this is more of an academic question.

**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).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

献世佛 2024-11-26 06:05:48

考虑这个小问题:

class A
{
    static void foo(){ }
}
class B extends A
{
    static void foo(){ }
}
void test()
{
    A.foo();
    B.foo();
}

假设我们从 B 中删除 foo 方法,并且只重新编译 B 本身,那么当我们运行 时会发生什么>测试()?它是否应该因为找不到 B.foo() 而抛出链接错误?

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

相反,让我们从 B 中删除 foo 方法,然后重新编译所有方法。即使编译器静态地知道 B.foo() 实际上意味着 A.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) 之间不会产生混淆。另请参阅 Java 中的类型擦除和重载:为什么这有效? 这个技巧只在 Java 6 中允许。Java 7 禁止它,可能是出于技术原因以外的原因。

Consider this trivial problem:

class A
{
    static void foo(){ }
}
class B extends A
{
    static void foo(){ }
}
void test()
{
    A.foo();
    B.foo();
}

Suppose we remove the foo method from B, and we only recompile B itself, what could happen when we run test()? Should it throw linkage error because B.foo() is no found?

According to JLS3 #13.4.12, removing B.foo doesn't break binary compatibility, because A.foo is still defined. This means, when B.foo() is executed, A.foo() is invoked. Remember, there's no recompilation of test(), so this forwarding must be handled by JVM.

Conversely, let's remove foo method from B, and recompile all. Even though compiler knows statically that B.foo() actually means A.foo(), it still generate B.foo() in the bytecode. For now, JVM will forward B.foo() to A.foo(). But if in future B gains a new foo method, the new method will be invoked at runtime, even if test() isn't recompiled.

In this sense, there is a overriding relation among static methods. When compile sees B.foo(), it must compile it to B.foo() in bytecode, regardless whether B has a foo() today.

In your example, when compiler sees BigCage.printList(animalCage), it correctly infer that it's actually calling Cage.printList(List<?>). So it needs to compile the call into bytecode as BigCage.printList(List<?>) - the target class must be BigCage here instead of Cage.

Oops! Bytecode format hasn't been upgrade to handle method signature like that. Generics information are preserved in bytecode as auxilary information, but for method invocation, it's the old way.

Erasure happens. The call is actually compiled into BigCage.printList(List). Too bad BigCage also has a printList(List) after erasure. At runtime, that method is invoked!

This problem is due to the mismatch between Java spec and JVM spec.

Java 7 tightens up a little; realizing bytecode and JVM can't handle such situations, it no longer compiles your code:

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

Another fun fact: if the two methods have different return types, your program will work correctly. This is because in byte code, method signature includes return type. So there is no confusion between Dog printList(List) and Object printList(List). See also Type Erasure and Overloading in Java: Why does this work? This trick is only allowed in Java 6. Java 7 forbids it, probably for reasons other than technical ones.

水溶 2024-11-26 06:05:48

这不是一个错误。该方法是静态的。您不能覆盖静态方法,只能隐藏它们。

当您在 bigCage 上调用“printList”时,您实际上是在 BigCage 类上调用 printList,而不是对象,该对象将始终调用 BigCage 类中声明的静态方法。

This is not a bug. The method is static. You cannot override static methods, you only hide them.

When you call "printList" on bigCage, you really are calling printList on BigCage class and not the object, which will always call your static method declared in BigCage class.

挽你眉间 2024-11-26 06:05:48

这是此代码的最简单版本,具有相同的问题:

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

Calling java GenericTestsClean:

javac 1.6.0_10 version

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 version

BigCage#printList
BigCage: class Cat
BigCage: class Dog

恕我直言,这个结果都是不正确的。

This is simplest version of this code with the same problem:

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

I think that compiller should return error:

    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

(or sth about name clash with the same errasure) but it doesn't.
After dissasembling (javap -c GenericTestsClean) we got:

invokestatic    #9; //Method BigCage.printList:(Ljava/util/List;)V

Calling java GenericTestsClean:

javac 1.6.0_10 version

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 version

BigCage#printList
BigCage: class Cat
BigCage: class Dog

IMHO this results are both incorrect.

爱殇璃 2024-11-26 06:05:48

恕我直言,此代码可能不正确。 类 BigCage 中的方法 printList 应该会导致名称冲突,因为 Cage 中的 printList 具有相同的擦除,但两者都不会覆盖另一个。编译器编译它很奇怪:)

生成的字节码(javac 1.6.0_10)相当于this:

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

或者源代码没问题,但编译器创建了错误的字节码?
事实上,我们调用方法 void printList(List list) 带有参数 BigCage; AnimalCage 和 Animal 扩展 Dog。

IMHO this code may be incorrect. Method printList in class BigCage should cause name clash coz printList in Cage have the same erasure, yet neither overrides the other. Strange that compiller compiles it :)

The resulting bytecode (javac 1.6.0_10) is equivalent to this:

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

The cast in loop causes exception.
Eclipse built-in compiller generates such code (wich works without exception):

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

Or maybe source is OK, but compiler is creating bad bytecode?
The fact is that we call method <U extends Dog> void printList(List<U> list) with parameter BigCage<Animal> animalCage and Animal does not extend Dog.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文