2

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 ....
marcomeyer
  • 178
  • 7
  • 1
    When you use a raw `B` then every generic information will be ignored, even if isn't related to ``. There is a suitable duplicate for this question ... looking for it right now. – Tom Jun 08 '16 at 13:12
  • @tom: I think the problem is the same, but the question asks about the compiler warning with enabled -Xlint:unchecked compiler flag. Actually this is a good thing to enable to detect these issues at compile time. – marcomeyer Jun 08 '16 at 13:50
  • Regarding duplicates: don't focus that much about the question itself, the important part is the answer (or "are the answers" if there are several of them). If they also answer your question, then it is a suitable duplicate :). – Tom Jun 08 '16 at 13:58

1 Answers1

2

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.

resueman
  • 10,572
  • 10
  • 31
  • 45