MAREK MAREK - 1 year ago 144
Java Question

Java Generics - extending generic class with generic function

I have a simple program like this:

package test;

public class TestGenericsInheritance {

public static void main(String[] args) {}

public static abstract class A<Q>{

public void foo2(Q obj){}
public abstract <T> void foo(T obj);

public static class C extends A<Object>{

public <T> void foo(T obj) {}

public static class B extends A{

public <T> void foo(T obj) {}

As you can see It does absolutely nothing. Compilation of this program files on java 1.6 and 1.7 with following error:

test.TestGenericsInheritance.B is not abstract and do es not override
abstract method foo(java.lang.Object) in
/D:/Projects/.../test/[27,25] name clash:
foo(T) in test.TestGenericsInheritance .B and foo(T) in
test.TestGenericsInheritance.A have the same erasure, yet neither
overrides the other
/D:/Projects/.../test/[26,9] method does
not override or implement a method from a supertype

Classes C and B are semantically identical, however class B doesn't recognize method foo as implementation of A#foo. To make this code compliant I have to implement method A#foo in class B with following signature:

public void foo(Object obj)

My question is, why my program doesn't compile? Generic types Q and T are completely independent, so why I may implement generic function A#foo only when I explicitly specify generic type Q for the inherited class A?


Answer Source

B extends the raw type A. Because you use a raw type, all generic information is erased, not only the type variable you failed to specify (see Section 4.6 of the Java Language Specification). This means that A has a method void foo(Object obj), whereas A<SomeType> has a method <T> void foo(T obj).

If you override a method, it must have the same signature (optionally after taking the type erasure of the overridden method) -- interestingly, the overriding method may have a different, more specific, return type. The signatures of your two methods are different (you would need type erasure in the overriding method), and therefore your example doesn't compile.

The reason type erasure was implemented as it is, is backwards compatibility. The idea was that new code would only use generics, and that only old code would use raw types. So, the goal was not to make raw types the most convenient (because new code shouldn't use them anyway), but to make raw types the most compatible.

Consider for example the class ArrayList, which was introduced in Java 2 (well before generics). It had a method public Object[] toArray(Object[] a). In Java 5 generics were introduced, and the signature of this method could be changed to public <T> T[] toArray(T[] a) (where T is not the element type) without difficulty: because of the way type erasure is implemented, for old code which used (or subclassed) ArrayList rather than ArrayList<SomeType> the method signature stayed the same.