3

I am searching for a solution that allows me to add Deques of any inner type to my List. Now the following code is possible.

/*
 * Please ignore the missing instantiations. It's only a technical
 * precompiler demonstration.
 */

final Deque<String> concreteDeque = null;
final Deque<?> unclearDeque = null;

final List<Deque<?>> list = null;

/*
 * The precompiler demands a parameter of type Deque<?>. That means any
 * Deque. Great! :)
 */
list.add(concreteDeque); // no precompiler error
list.add(unclearDeque); // no precompiler error

final Deque<?> deque = list.get(0); // no precompiler error, great! :)

Now, I want to make my own more specific interface.

/**
 * A list that holds instances of {@link Deque}. Only for demonstrations.
 *
 * @author Barock
 *
 * @param <DT>
 *            The type that should pass to the {@link Deque}.
 */
public interface DequeList<DT> extends List<Deque<DT>> { }

But with this interface my code doesn't work anymore.

final Deque<String> concreteDeque = null;
final Deque<?> unclearDeque = null;

final DequeList<?> dequeList = null;

// Now the precompiler announces wildcard capture for adding elements!?
dequeList.add(concreteDeque); // precompiler error
dequeList.add(unclearDeque); // precompiler error

final Deque<?> deque = dequeList.get(0); // still no precompiler error. :)

I assume that my DequeList<?> is equivalent to the List<Deque<?>>. Why obviously not?

Michael
  • 41,989
  • 11
  • 82
  • 128
Barock
  • 427
  • 1
  • 4
  • 12

2 Answers2

3

There is an difference between List<?> and List<List<?>> (in place of list you can put any generic collection).

List<?> is designed for read-only purposes. Suppose List<?> already contains an elements of "some-type". As "some-type" is too general, you can not be sure what type it is and so you can not simply add anything in due of type safety, otherwise list would contain heterogenous elements (of different types).

Consider following example:

List<String> someLetters = Arrays.asList("a", "b", "c", "d", "e");
List<Integer> someInts = Arrays.asList(1, 2, 3, 4, 5);

displayContent(someLetters);
displayContent(someInts);

public static void displayContent(List<?> list) {
    System.out.println(list);
}

Would it be legal to add say an Integer in someLetters when this list is casted to List<?>? Maybe in some other language it would be possible, but not in Java as Collection of specified type has to be homogenous. Note <?> specifies "some-type" which is hidden, but it definitely does not mean that you can add in anything you want. In example above, method displayContent is not aware of list's true type, thus any add is illegal:

public static void displayContent(List<?> list) {
    // illegal as it may break list's homogenity...
    list.add(1);   
    // illegal as it may break list's homogenity...
    list.add("f");
    System.out.println(list);
}

In case of List<List<?>> you have list composed of read-only lists which allows you to add any read-only list you want but disallows you to add something in list obtained from this "list of lists", that is, you can't do something like this list.get(0).add(something); for reason stated above.

EDIT

I have no idea why DequeList<?> is not equivalent to List<Deque<E>, however I've just spotted another thing which might be useful for someone else who will try to solve this mistery. For some reason, you can't add deque of some type i. e. Deque<?> in DequeList<?>, but you can add deque of no type i. e. Deque in DequeList<?>:

Deque<String> concreteDeque = null;
Deque<?> unclearDeque = null;
Deque rawDeque = null;

List<Deque<?>> list = null;
list.add(concreteDeque); // OK
list.add(unclearDeque);  // OK
list.add(rawDeque);      // OK

DequeList<?> myList = null; // should be equivalent to List<Deque<?>> 
myList.add(concreteDeque);  // ERROR
myList.add(unclearDeque);   // ERROR
myList.add(rawDeque);       // OK

It seems that after "extends" a Deque<?> became a strong type of DequeList. Since DequeList may contain deques of particular type e. g. Deque<String> , you can not add Deque of any other type that is specified or hidden in <?>. Unfortunately, it does not answer why it is possible to add rawDeque.

matoni
  • 2,479
  • 21
  • 39
  • Sure, but theoretically the compiler could figure out that `DequeList>` is semantically the same as `List>` and therefore anything that is safe for one is safe for the other. I think there's another mechanism at play here. – Dici Dec 16 '17 at 14:09
  • @Dici you are right, didn't read question carefully (my bad). I'll try to figure out what is going on here :). – matoni Dec 16 '17 at 14:12
  • I still upvoted your answer because it's helpful to understand the role of the wildcard – Dici Dec 16 '17 at 14:13
  • `rawDeque` is, as its name indicates, raw, therefore the compiler does not perform any check on it, that's why you can add it. – Dici Dec 16 '17 at 16:34
  • @Dici `Object` also can be seen as "raw" (in some sense) but we can not add `Object` in `List>`. I think this question deserves an attension of some Java engineer or at least someone who studied Java Language Specification. – matoni Dec 16 '17 at 16:38
  • `Object` is not generic, *raw* only applies to generic classes or methods used without explicitly specifying their generic parameter. Otherwise I agree with the last part of your statement, I'm not deep enough in Java compiler details to answer this question. – Dici Dec 17 '17 at 21:26
1

I have upvoted matoni's answer since he's explaining most of the problem, but I believe his explanation is incomplete.

True, List<?> will refuse any generic parameter, in particular it will refuse any call to the add method, which makes it effectively read-only. The exact same thing happens to DequeList<?>, which is also read-only.

However a human being can easily tell that whatever is safe for List<Deque<?>> is also safe form DequeList<?>, and vice-versa. The compiler is unable to figure this out, why is that ?

Let's try something: what if we tried to instantiate our DequeList and give it the same declaration type as the one you used in your first snippet ? If we manage to declare if with the same type, the compiler will allow us the same operations on it.

public static class DequeList<DT> implements List<Deque<DT>> {
    // implement all methods with default implementations to make the compiler happy
}

// oups, doesn't compile: "Wildcard type '?' cannot be instantiated directly"
List<Deque<?>> dequeList = new DequeList<?>();

Ok it didn't work. Apparently, the wildcard can only be used to change the declaration type of an existing generic object, not to instantiate a new object. Let's try something else:

DequeList<?> dequeList1 = null;
// again, doesn't compile: "Incompatible types. Required: List<java.util.Deque<?>>. Found: DequeList<capture<?>>"
List<Deque<?>> dequeList2 = dequeList1;

So it seems List<Deque<?>> and DequeList<?> are simply incompatible types for the compiler.

I don't have a full explanation for this but it seems like a limitation of the compiler which fails to infer those types are in fact the same, I don't think there is a fundamental reason why this should not compile.

Dici
  • 25,226
  • 7
  • 41
  • 82