Java,静态方法绑定和泛型都与一些方法重载汇总 [英] Java, Static Method Binding and Generics all rolled up with some Method Overloading

查看:139
本文介绍了Java,静态方法绑定和泛型都与一些方法重载汇总的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

正如标题所暗示的,我的问题有点奇怪和复杂。我知道我要做什么打破了良好编程习惯的所有规则,但是,嘿,如果我们活不下去,那么生活是什么?

我做的是创建以下程序。 (不是这是一个更大的实验的一部分,要真正尝试和理解泛型,所以一些函数名可能有点乱)

  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)
{
//这将打印
System.out。 println(\\\
Test 2);
test2();
}

}

class BigCage< T>延伸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>扩展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延伸动物
{
}
class Cat扩展Animal
{
}

现在令我困惑的是是这样可以很好的用 javac 1.6.0_26 编译,但是当我运行它时,我得到了下面的类转换异常:

 测试2 
************* class BigCage
BigCage:class Dog
************* class BigCage
线程main中的异常java.lang.ClassCastException:Cat无法在BigCage.printList中转换为Dog
(GenericTestsClean.java:31)$ GenericTestsClean.test2中的
(GenericTestsClean。 (GenericTestsClean.java:21)

一个数字在这里需要注意的是:


  1. 两个printList是 NOT 覆盖,但如预期的那样相互重载不同的类型,因为它们的参数的泛型是不同的出租)。这可以通过使用@Override注解来验证

  2. 中更改 void printList(List <?>) class Cage 非静态生成适当的编译时间错误
  3. 更改方法 void< U extends Dog>在 class BigCage void< U>中的printList(List printList(List 会生成相应的错误。

  4. main()通过类BigCage (例如BigCage.printList(...))生成相同的运行时错误 main()通过类笼(即Cage.printList(...))调用 printList(),只会调用 printList 笼子

  5. 如果我将 printList(List<>)的定义复制到 class Cage 中的class BigCage ,它将隐藏类Cage 中的定义,我得到相应的编译器错误

  6. ol>

    现在,如果我不得不在这里发生什么,我会说编译器正在崩溃,因为它分多个阶段工作: >类型检查重载的方法解析。在类型检查阶段,我们通过违规行,因为 class BigCage void printList(List <?>>) > class Cage ,它将匹配我们抛出的所有旧列表,所以确保我们有一个可以工作的方法。然而,一旦实际调用的方法解决,我们就有一个问题,因为Type Erasure会导致 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中出现编译时错误,我无法看到为什么
    它应该工作,当同样的方法被继承和手动操作时$添加b $ b(参见上面的<注意6> )。


    解决方案

    考虑这个小问题:

      class A 
    {
    static void foo(){}

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

    $ / code>

    假设我们删除了 foo B 本身,当我们运行时,会发生什么情况>试验()?是否应该抛出链接错误,因为找不到 B.foo()?根据JLS3#13.4.12,删除 B.foo 不会破坏二进制兼容性,因为<$ <

    c $ c> A.foo 仍然定义。这意味着,当执行 B.foo()时,会调用 A.foo()。记住,不需要重新编译 test(),所以这个转发必须由JVM来处理。



    B 中删除​​ foo 方法,并重新编译全部。即使编译器静态地知道 B.foo()实际上意味着 A.foo(),它仍然会生成<$字节码中的c $ c> 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不能处理这种情况,它不再编译你的代码:

    lockquote

    错误:name冲突:
    printList (List)在BigCage中和
    printList(List)在Cage中有
    相同的擦除,但既不隐藏
    其他


    另一个有趣的事实是:如果这两种方法有不同的返回类型,你的程序将正常工作。这是因为在字节码中,方法签名包含返回类型。因此,在 Dog printList(List) Object printList(List)之间没有混淆。另请参阅在Java中输入擦除和重载:为什么这会起作用?这个技巧只能在Java 6中使用.Java 7禁止它,可能出于技术原因以外的原因。


    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. (Not 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).

    解决方案

    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.

    这篇关于Java,静态方法绑定和泛型都与一些方法重载汇总的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆