Java, static method binding and generics are all involved in some method overloading
So the title means that my question is a little strange and complex I know what I'm going to do to break all the rules of "good" programming practice, but hey, what life if we don't live?
So what I do is create the following program (this is not part of a larger experiment to really try and understand generics, so some function names may be a little unqualified)
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 { }
What puzzles me now is that it uses javac 1.6 0_ 26, but when I run it, I will get the following class conversion exceptions:
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)
Here are some things to note:
>The two printlists will not be overwritten, but will be overloaded as expected (they have different types because their parameters have different generic types) This can be verified by using the @ override annotation > changing the void printlist (list ) method in the cage class to non static will generate appropriate compile time errors > changing the method void < u extends the printlist (list < U >) in the dog > class bigcage to generate appropriate errors for void < u > printlist (list < U >). > In main (), the same runtime error is generated by calling printList () from the BigCage class (BigCage.printList (...)) > printList () is invoked in main () by Cage (i.e. Cage.printList (...)). The working principle of the printList is only invoked in Cage. If I copy the definition of printList from the class to the class, this will hide the definition in the class. I got the corresponding compiler error
Now, if I had to shoot what happened here in the dark, I would say that the compiler is tightening because it works in multiple stages: type checking and overloaded method parsing During type checking, we passed the violation line. Because bigcage class inherits void printlist (list ) in cage class, it will match any old list we throw, so we ensure that we have a working method However, once we encounter the method actually called to solve the problem, we will encounter the problem of type erasure, which will lead to bigcage Printlist and cage Printlists have identical signatures This means that when the compiler is looking for a matching animalcage printList(animalCage); It will choose the first method of its matching (if we assume that it uses bigcage from the bottom and takes its reason as object), it will find void < u extends dog > printlist (list < U >) instead of the correct matching void printlist (list )
Now my real question: am I close to the truth here? Is this a known error? Is this a bug? I know how to solve this problem. It's more like an academic problem
Solution
Consider this trivial question:
class A { static void foo(){ } } class B extends A { static void foo(){ } } void test() { A.foo(); B.foo(); }
Suppose we delete the foo method from B, and we recompile only B itself. What happens when we run test()? Will link errors be found because b.foo() was not found?
According to jls3#13.4 12. Deleting b.foo will not destroy binary compatibility because A.Foo is still defined This means that when B. foo () is executed, A. foo () is called Remember, test () is not recompiled, so this forwarding must be handled by the JVM
Instead, we remove the foo method from B and recompile all Even if the compiler statically knows that B. foo () actually means A. foo (), it still generates B. foo () in bytecode Now, the JVM forwards B. foo () to A. foo () However, if B obtains a new foo method in the future, even if test () is not recompiled, the new method will be called at run time
In this sense, there is an overriding relationship between static methods When the compiler sees b.foo(), it must compile it into b.foo() in by code, regardless of whether B has a foo()
In your example, when the compiler sees bigcage When printlist (animalcage), it correctly infers that it is actually calling cage printList(List<?>). Therefore, you need to compile the call as bigcage. Exe Bytecode of printlist (list ) – the target class must be bigcage instead of cage
oh dear! The bytecode format has not been upgraded to handle method signatures Generic information is saved as auxiliary information in bytecode, but it is the old way for method calls
Erasure occurs This call is actually compiled into bigcage printList(List). Too bad. Bigcage also has a printlist (list) after erasing At runtime, the method is called!
This problem is caused by a mismatch between the Java specification and the JVM specification
Java 7 tightened a little; The implementation bytecode and JVM cannot handle this situation. It will no longer compile your code:
Another interesting fact: if the two methods have different return types, your program will work normally This is because in bytecode, the method signature includes the return type Therefore, there is no confusion between dog printlist (list) and object printlist (list) See also type error and overloading in Java: why does this work? This technique is only allowed in Java 6 Java 7 prohibits it, possibly for reasons other than technology