Java, Static Method Binding and Generics all rolled up with some Method Overloading

匆匆过客 提交于 2019-12-05 03:29:11
irreputable

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.

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.

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.

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!