Marco Meyer Marco Meyer - 5 months ago 12
Java Question

Why does an unspecified Type in generics destroy type safety for non generic methods

Can somebody explain me, why a simple

<T>
in my interface destroys type safety at compile time? See following example:

public class GenericsMain {

public static void main(String[] args){
A someA = new A() {
@Override
public List<String> listOfStrings() {
return Arrays.asList("A");
}
};
B someB = new B() {
@Override
public List<String> listOfStrings() {
return Arrays.asList("B");
}
};
List<Long> listOfLong = null;
//listOfLong = someA.listOfStrings(); // compile error (expected)

listOfLong = someB.listOfStrings(); // NO COMPILE ERROR. Why !?

for(Long l : listOfLong){ // here I get ClastCastException of course.
System.out.println(l);
}
}

interface A{
List<String> listOfStrings();
}

interface B<T>{
List<String> listOfStrings();
}
}


Also interesting is, that if a type for
<T>
is specified, the compiler complains again correctly. So it looks like generics also affect non-generic method declarations!?

B<Integer> someBOfInteger = null;
listOfLong = someBOfInteger.listOfStrings(); // compiler complains correctly


Update after correct answers:



So if one needs to extend a type with generics it is better to really create a subclass/subinterface and add the generic type in the subclass. So in the above example one could add a generic method to A by

interface C<T> extends A {
T genericMethod(T p);
}


Also as indicated by the referenced question, it is a good idea to use the compiler flag:

javac -Xlint:unchecked ....

Answer

You can find the answer is section 4.8 of the JLS

The type of a constructor (§8.8), instance method (§8.8, §9.4), or non-static field (§8.3) M of a raw type C that is not inherited from its superclasses or superinterfaces is the erasure of its type in the generic declaration corresponding to C. The type of a static member of a raw type C is the same as its type in the generic declaration corresponding to C.

If you create an instance of a generic class without giving it a generic type, it becomes a raw type. And, because of that section of the JLS, all of its (non-inherited) fields become erased as well. So it's as though you declared your interface as this:

interface B{
    List listOfStrings();
}

which leads to the error you see. The generic type of the List being returned gets erased, even though you explicitly specify it. This is one of the reasons why raw types should always be avoided.