Codebender Codebender - 1 year ago 66
Java Question

Irregularities with the (?) wildcard generic type

I believe that the type

in generics is a specific unknown type. Which means, declaring let's say a list of that type would prevent us from adding any type of object into it.

List<?> unknownList;
unknownList.add(new Object()); // This is an error.

The compiler gives an error as expected.

But when the unknown type is a second level generics, the compiler doesn't seem to care.

class First<T> {}

List<First<?>> firstUnknownList;

// All these three work fine for some reason.
firstUnknownList.add(new First<>());
firstUnknownList.add(new First<Integer>());
firstUnknownList.add(new First<String>());

I thought probably the compiler doesn't care about generic parameter in the second level at all, but it's not the case,

List<First<Integer>> firstIntegerList;
firstIntegerList.add(new First<String>()); // This gives a compiler error as expected.

So, why does the compiler allow us adding any kind of element when only an unknown element (and hence nothing) is acceptable in the second example?

Note: Compiler Java 1.8

Answer Source

You can add anything to a List<T> that you can store in a reference of type T:

T item = ...
List<T> list = new ArrayList<>();

First<?> is a supertype of First<T>; so you can store a reference to a First<T> in a variable of type First<?>:

First<?> first = new First<String>();

So, substituting T for First<?> above:

First<?> item = new First<String>();
List<First<?>> list = new ArrayList<>();

All that is happening in OP's example is that the temporary variable item is omitted:

firstUnknownList.add(new First<String>());

However, if you do this with the firstIntegerList example:

First<Integer> item = new First<String>(); // Compiler error.
List<First<Integer>> list = new ArrayList<>();

it is clear why that's not allowed: you can't make the assignment of item.

It's also possible to see that you can't do anything unsafe with the contents of that list.

If you add a couple of methods to the interface:

interface First<T> {
  T producer();
  void consumer(T in);

Now, consider what you can do with the elements that you added to the list:

for (First<?> first : firstUnknownList) {
  // OK at compile time; OK at runtime unless the method throws an exception.
  Object obj = first.producer();

  // OK at compile time; may fail at runtime if null is not an acceptable parameter.

  // Compiler error - you can't have a reference to a ?.
  first.consumer(/* some maybe non-null value */);

so there isn't actually anything that you can really do with elements of that list that would violate type safety (provided you don't do anything willful to violate it, like using raw types). You can demonstrate that generic producer/consumer methods are similarly safe or forbidden by the compiler.

So there is no reason not to allow you to do this.