Something You Didn’t Know About Generics

Before the introduction of Generics in Java 5 it’s commonly known that in a class you can’t have two methods with the same signature that differ by return type alone.  More formally, the Java Language Specification §8.4.2 says that “it is a compile time error to declare two methods with override-equivalent signatures in a class”.  Take for example the following code which does exactly this and unsurprisingly doesn’t compile:

 1 // Generates error "myMethod(java.util.List) is already defined
 2 // in DoesNotCompile when compiled with Sun JDK version 1.5.0_13.
 3 abstract class DoesNotCompile {
 4     abstract String myMethod(List listOfStrings);
 6     abstract Integer myMethod(List listOfIntegers);
 7 }

Code Listing 1

It might be surprising though that a class with the same methods changed slightly to use Generics compiles without errors:

 1 abstract class Compiles {
 2     abstract String myMethod(List<String> listOfStrings);
 4     abstract Integer myMethod(List<Integer> listOfIntegers);
 5 }

Code Listing 2

Without knowing better, one could dismiss the two method signatures above as different; after all, one uses a type parameter of java.lang.String and the other java.lang.Integer.  But Generics works by type erasure, where specific type information like List<String> is erased by the compiler to become just List.  Type erasure of the parameter types of the two methods in Listing 2 results in the same type —java.util.List— for both methods.

So the two methods in Compiles really do differ solely by return type yet aren’t override-equivalent. Why this is so isn’t clear.  JLS §8.4.2 and an evaluation in a Sun bug report explain that it’s because the method erasures aren’t the same, but unfortunately there aren’t any details given about how a method erasure is computed exactly.  Whatever the algorithm is, we now know empirically that the method return type matters.