views:

283

answers:

7

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();
        ArrayList<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);
    }
}
+2  A: 

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.

dty
Eclipse is wrong. This is perfectly legal Java.
erickson
Meh. I was purely guessing based on historical averages. Given other people's analysis, I suspect you're right. Either way, raising this with the Eclipse guys is the way forward.
dty
+1  A: 

Are you sure that Eclipse is also set to use Java 1.6?

Jeffrey
Out of interest, why do you think that's relevant? Eclipse is set to at least 1.5 or else it wouldn't compile at all. Do you think there's some significant difference between the language semantics in 1.5 and 1.6?
dty
I just wanted to make sure it wasn't set to 1.4. I don't use eclipse, but I know that IDE's usually have a java version setting independent of your system one.As far as 1.5-1.6 differences, I don't know of any significant ones.
Jeffrey
If it was set to 1.4 it wouldn't have compiled at all due to the generics.
dty
A: 

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.

JGord
A little loose with the language here. To be clear, javac IS the compiler that Sun (Oracle) publishes. And Eclipse doesn't use a third-party compiler - it uses its own compiler implementation.
dty
+1  A: 

Works for me in Eclipse Helios. Method selection occurs at compile time, and the compiler has enough information to do so.

Jim Garrison
+4  A: 

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>]
trashgod
After type erasure, the method signatures become `public String getFirst(ArrayList)` and `public Integer getFirst(ArrayList)`.
McDowell
No McDowell, method signatures are not erased.
meriton
@meriton: @McDowell: IIUC, you are both corect: the signatures are erased _and_ the generic type parameters are preserved, as suggested above.
trashgod
A: 

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

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. For more information, see my answer.
meriton
+5  A: 

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?

meriton
I think the case is much closer than this. Consider: If you change the return types of both methods to Object, the code will no longer compile. The JLS doesn't say that return type can be a distinguishing feature on a method signature to determine override-equivalency. I agree ultimately that the Sun compiler is correct, but it is a very close call.
Yishai
Good point, I have edited to include the relevant section. The rule is question is not about override equivalence (where return types are specified to not matter), but more general in nature.
meriton