Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java generics code compiles with javac, fails with Eclipse Helios

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);
    }
}
like image 230
Deech Avatar asked Aug 10 '10 19:08

Deech


2 Answers

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?

like image 54
meriton Avatar answered Sep 27 '22 19:09

meriton


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>]
like image 38
trashgod Avatar answered Sep 27 '22 19:09

trashgod