Why ListList<? super E> is List<? extends List<? super E>> but not List<List<? super E>>

Bananon :

I have a generic interface interface ListList<E> extends List<List<E>>. For some reasons, I can't cast ListList<? super T> to List<List<? super T>>. Is there any way of doing it and why it doesn't work?

By this moment I've already tried the following:

  1. Simple assignment, this way I've managed to assign ListList<? super T> to List<? extends List<? super T>> (1), but when I try to assign ListList<? super T> to List<List<? super T>> I get Incompatible types compile-time error (1.1).
  2. Explicit type conversion, it doesn't work because of the same Incompatible types compile-time error (2).
  3. Casting to raw type ListList, it works (3), but I don't like raw types.
  4. Adding all elements from ListList<? super T> to List<? extends List<? super T>>, it works (4), but I need a more general solution which works not only with ListList<E>, but with any generic type.

Here is my code:

ListList<? super T> var = new ArrayListList<>();
List<? extends List<? super T>> work = var; // (1)
List<List<? super T>> notWork = var; // (1.1)
List<List<? super T>> explicit = (List<List<? super T>>) var; // (2)
List<List<? super T>> raw = (ListList) var; // (3)
List<List<? super T>> copy = new ArrayList<>(); // (4)
copy.addAll(var); // (4)

I've expected ListList<? super T> to be List<List<? super T>>, but it appeared to be List<? extends List<? super T>>. I need to know why it is and how I can cast it to List<List<? super T>> without raw types and copying of elements.

rgettman :

At first, it looks like these assignments should all succeed, but they don't because of the inner wildcard ? super T. If we remove those wildcards, then all the assignments compile.

public static <T> void test() {
    ListList<T> var = new ArrayListList<>();
    List<? extends List<T>> work = var; // Compiles
    List<List<T>> notWork = var; // Compiles
    List<List<T>> explicit = (List<List<T>>) var; // Compiles
    List<List<T>> raw = (ListList) var; // Compiles with warning
    List<List<T>> copy = new ArrayList<>(); // Compiles
    copy.addAll(var); // Compiles
}

I still get the unchecked conversion warning for (3), but they all still compile.

At first glance it looks like declaring the interface

ListList<E> extends List<List<E>>

makes a ListList equivalent to a List of Lists. However what you have done is take a nested type parameter and made it the main type parameter. The reason that that makes a difference is nested wildcards don't perform wildcard capture.

A nested wildcard here means "a list of lists of any type matching the bound", but a main-level wildcard here means "a 'listlist' of a specific yet unknown type matching the bound".

One cannot add an object that is a supertype of the lower bound to a collection, because the type parameter -- a specific yet unknown type -- may be the actual bound.

List<? super Integer> test2 = new ArrayList<>();
test2.add(2);   // Compiles; can add 2 if type parameter is Integer, Number, or Object
test2.add((Number) 2);   // Error - Can't add Number to what could be Integer
test2.add(new Object()); // Error - Can't add Object to what could be Integer

Because Java's generics are invariant, the types must match exactly when type parameters are involved, so similar cases for ListList all fail to compile.

// My assumption of how your ArrayListList is defined.
class ArrayListList<E> extends ArrayList<List<E>> implements ListList<E> {}

ListList<? super Integer> llOfSuperI = new ArrayListList<>();
llOfSuperI.add(new ArrayList<Integer>());  // capture fails to match Integer
llOfSuperI.add(new ArrayList<Number>());   // capture fails to match Number
llOfSuperI.add(new ArrayList<Object>());   // capture fails to match Object

However, a List of List compiles with all 3 cases.

List<List<? super Integer>> lOfLOfSuperI = new ArrayList<>();
lOfLOfSuperI.add(new ArrayList<Integer>());  // no capture; compiles
lOfLOfSuperI.add(new ArrayList<Number>());   // no capture; compiles
lOfLOfSuperI.add(new ArrayList<Object>());   // no capture; compiles

Your ListList is a different type than a List of Lists, but the differing generics behavior of where the type parameter is defined means that there is different generics behavior. This is why you cannot directly assign a ListList<? super T> to a List<List<? super T>> (1.1), and also why you can't cast it (2). You can cast to a raw type to get it to compile (3), but that introduces the possibilities of ClassCastException in future use of the casted object; that is what the warning is about. You can assign it to a List<? extends List<? super T>> (1), introducing another wildcard to capture a subtype relationship, but that introduces a wildcard to be captured; you won't be able to add anything useful to that list.

These differences have arisen only because a wildcard introduces wildcard capture and the associated differences. Without using a wildcard, a ListList<E> is equivalent to a List<List<E>> and as shown at the top of this answer, shows no problems compiling the code.

If you want all of your sub-lists to use the same exact type parameter, then go ahead and use your ListList interface, but don't use any wildcards. This forces the exact same type parameter for all lists that are added to your ListList, i.e. a ListList<Integer> can only hold List<Integer>s.

If you want all of your sub-lists to simply match a wildcard, e.g. contain a List<Number>, List<Integer>, and List<Object> in the same list, then just use a List<List<? super T>> to avoid wildcard capture.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=121064&siteId=1