Java - Behavior of Class Members of Generic Classes

generics, java

Solution

Because you used a raw `Test`, all generics in the `Test` class are effectively type-erased, even unrelated generics such as returning `Set<Integer>`, so that the `Set<Integer>` returned by `getInts` becomes just a raw `Set`. That explains why you get incompatible types, because a raw `Set` will return `Object`s, not `Integer`s.

Section 4.8 of the JLS covers raw types:

The type of a constructor (§8.8), instance method (§8.4, §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 raw type that corresponds to the erasure of its type in the generic declaration corresponding to C.

and

The use of raw types is allowed only as a concession to compatibility of legacy code. The use of raw types in code written after the introduction of generics into the Java programming language is strongly discouraged. It is possible that future versions of the Java programming language will disallow the use of raw types.

(emphasis mine)

The reason for this behavior is for backwards compatibility with pre-generics Java code, because the pre-generics versions of built-in Java classes are the same things as the raw type versions of the generified built-in classes. That is, a pre-generics `Set` is the same thing as a raw `Set` (generics removed).

Problem

If you declare an instance of a generic class as a raw type in Java, does the compiler assume a parametrized type of `Object` for all class member methods? Does this extend even to those methods which return some form (e.g. a `Collection`) of a concrete parametrized type? I erroneously declared an instance of a generic class without a parametrized type, which led to very interesting downstream effects. I've provided a simplified example of a scenario which produced an 'incompatible types' compilation error. My basic question: Why does javac produce an 'incompatible types' error, complaining about the indicated line below? ``` import java.util.*; public class Main { public static void main(String[] args) { final Test<String> t1 = new Test<String>(); final Test t2 = new Test<String>(); for (final Integer i : t1.getInts()) { System.out.println(i); } for (final Integer i : t2.getInts()) { //<-- compile-time error System.out.println(i); } } public static class Test<T> { public Test() { } public Set<Integer> getInts() { final Set<Integer> outSet = new HashSet<Integer>(); outSet.add(new Integer(1)); outSet.add(new Integer(2)); outSet.add(new Integer(3)); return outSet; } } } ``` An interesting note is that if `t2` is declared with a wildcard type (`Test<?> t2…`) the code compiles and runs as expected. The use of the for-each loop invokes `next()` on the iterator on the set returned by `getInts()` (which returns a generic type gleaned from the Iterable being iterated over). The documentation here mentions that if the Iterable is raw, `Object`is used as the return type. Why does the compiler also seem to change the return type of `Test.getInts()` from `Set<Integer>` to `Set<Object>`?

Original source