I have the following test class that uses generics to overload a method. It works when compiled with javac and fails to compile in Eclipse Helios. My java version is 1.6.0_21.
All the articles I read indicate that Eclipse is right and this code should not work. However when compiled with javac and run, the right method is selected.
How is this possible?
Thanks!
import java.util.ArrayList;
public class Test {
public static void main (String [] args) {
Test t = new Test();
开发者_StackOverflowArrayList<String> ss = new ArrayList<String>();
ss.add("hello");
ss.add("world");
ArrayList<Integer> is = new ArrayList<Integer>();
is.add(1);
is.add(2);
System.out.println(t.getFirst(ss));
System.out.println(t.getFirst(is));
}
public String getFirst (ArrayList<String> ss) {
return ss.get(0);
}
public Integer getFirst (ArrayList<Integer> ss) {
return ss.get(0);
}
}
The Java Language Specification, section 8.4.2 writes:
It is a compile-time error to declare two methods with override-equivalent signatures (defined below) in a class.
Two method signatures m1 and m2 are override-equivalent iff either m1 is a subsignature of m2 or m2 is a subsignature of m1.
The signature of a method m1 is a subsignature of the signature of a method m2 if either
m2 has the same signature as m1, or
the signature of m1 is the same as the erasure of the signature of m2.
Clearly, the methods are not override equivalent, since ArrayList<String>
isn't ArrayList
(the erasure of ArrayList<Integer>
).
So declaring the methods is legal. Also, the method invocation expression is valid, as there trivially is a most specific method, since there is only one method matching the argument types.
Edit: Yishai correctly points out that there is another restriction closely skirted in this case. The Java Language Specification, section 8.4.8.3 writes:
It is a compile time error if a type declaration T has a member method m1 and there exists a method m2 declared in T or a supertype of T such that all of the following conditions hold:
- m1 and m2 have the same name.
- m2 is accessible from T.
- The signature of m1 is not a subsignature (§8.4.2) of the signature of m2.
- m1 or some method m1 overrides (directly or indirectly) has the same erasure as m2 or some method m2 overrides (directly or indirectly).
Appendix: On ersure, and the lack thereof
Contrary to popular notion, generics in method signatures are not erased. Generics are erased in bytecode (the instruction set of the Java virtual machine). Method signatures are not part of the instruction set; they are written to the class file as specified in the source code. (As an aside, this information can also be queried at runtime using reflection).
Think about it: If type parameters were erased from class files entirely, how could the code completion in the IDE of your choice display that ArrayList.add(E)
takes a parameter of type E
, and not Object
(=the erasure of E
) if you didn't have the JDKs source code attached? And how would the compiler know to throw a compilation error when the static type of the method argument wasn't a subtype of E
?
This code is correct, as described in JLS 15.12.2.5 Choosing the Most Specific Method.
Also, consider coding to the interface:
List<String> ss = new ArrayList<String>();
List<Integer> is = new ArrayList<Integer>();
// etc.
As @McDowell notes, the modified method signatures appear in the class file:
$ javap build/classes/Test
Compiled from "Test.java"
public class Test extends java.lang.Object{
public Test();
public static void main(java.lang.String[]);
public java.lang.String getFirst(java.util.ArrayList);
public java.lang.Integer getFirst(java.util.ArrayList);
}
Note that this does not contradict @meriton's observation about the class file. For example, the output of this fragment
Method[] methods = Test.class.getDeclaredMethods();
for (Method m : methods) {
System.out.println(Arrays.toString(m.getGenericParameterTypes()));
}
shows the formal parameter of main()
, as well as the two generic type parameters:
[class [Ljava.lang.String;]
[java.util.ArrayList<java.lang.String>]
[java.util.ArrayList<java.lang.Integer>]
Works for me in Eclipse Helios. Method selection occurs at compile time, and the compiler has enough information to do so.
It would be possible if javac had a bug in it. Javac is just software and is as liable to bugs as any other piece of software.
Plus, the Java Language Spec is very complex and a little bit vague in places so it could also be down to a difference in interpretation between the Eclipse guys and the javac guys.
I would start by asking this on the Eclipse support channels. They're normally very good at picking up these things and explaining why they think they're right or else admitting that they're wrong.
For the record, I think Eclipse is right here, too.
Are you sure that Eclipse is also set to use Java 1.6?
After some research, I have the answer:
The code, as it is specified above should NOT compile. ArrayList<String>
and ArrayList<Integer>
, at runtime is still ArrayList
. But your code is not working because the returning type. If you set the same returning types for both methods, javac won't compile that...
I read that there is a bug in Java 1.6 (that is already fixed in Java 1.7) about this error. Is all about returning types... so, you will need to change the signature of your methods.
There is the bug 6182950 in Oracle's Bug Database.
Eclipse and javac use different compilers. Eclipse uses a third party compiler to turn your code into bytecodes for the Java VM. Javac uses the Java compiler than Sun publishes. Therefore it is possible that identical code produces slightly different results. Netbeans I believe uses Sun's compiler so check it in there as well.
A point to keep in mind is that (all?, certainly some as for instance the NetBeans editor does this) static code analysis tools do not consider return type or modifiers (private/public etc.) as part of the method signature.
If that is the case, then with the aid of type erasure both getFirst
methods would get the signature getFirst(java.util.ArrayList)
and hence trigger a name clash...
精彩评论