Let's first consider a simple scenario (see complete source on ideone.com):
import java.util.*;
public class TwoListsOfUnknowns {
static void doNothing(List<?> list1, List<?> list2) { }
public static void main(String[] args) {
List<String> list1 = null;
List<Integer> list2 = null;
doNothing(list1, list2); // compiles fine!
}
}
The two wildcards are unrelated, which is why you can call doNothing
with a List<String>
and a List<Integer>
. In other words, the two ?
can refer to entirely different types. Hence the following does not compile, which is to be expected (also on ideone.com):
import java.util.*;
public class TwoListsOfUnknowns2 {
static void doSomethingIllegal(List<?> list1, List<?> list2) {
list1.addAll(list2); // DOES NOT COMPILE!!!
// The method addAll(Collection<? extends capture#1-of ?>)
// in the type List<capture#1-of ?> is not applicable for
// the arguments (List<capture#2-of ?>)
}
}
So far so good, but here's where things start to get very confusing (as seen on ideone.com):
import java.util.*;
public class LOLUnknowns1 {
static void probablyIllegal(List<List<?>> lol, List<?> list) {
lol.add(list); // this compiles!! how come???
}
}
The above code compiles for me in Eclipse and on sun-jdk-1.6.0.17
in ideone.com, but should it? Is it not possible that we have a List<List<Integer>> lol
and a List<String> list
, the analogous two unrelated wildcards situations from TwoListsOfUnknowns
?
In fact the following slight modification towards that direction does not compile, which is to be expected (as seen on ideone.com):
import java.util.*;
public class LOLUnknowns2 {
static void rightfullyIllegal(
List<List<? extends Number>> lol, List<?> list) {
lol.add(list); // DOES NOT COMPILE! As expected!!!
// The method add(List<? extends Number>) in the type
// List<List<? extends Number>> is not applicable for
// the arguments (List<capture#1-of ?>)
}
}
So it looks like the compiler is doing its job, but then we get this (as seen on ideone.com):
import java.util.*;
public class LOLUnknowns3 {
static void probablyIllegalAgain(
List<List<? extends Number>> lol, List<? extends Number> list) {
lol.add(list); // compiles fine!!! how come???
}
}
Again, we may have e.g. a List<List<Integer>> lol
and a List<Float> list
, so this shouldn't compile, right?
In fact, let's go back to the simpler LOLUnknowns1
(two unbounded wildcards) and try to see if we can in fact invoke probablyIllegal
in any way. Let's try the "easy" case first and choose the same type for the two wildcards (as seen on ideone.com):
import java.util.*;
public class LOLUnknowns1a {
static void probablyIllegal(List<List<?>> lol, List<?> list) {
lol.add(list); // this compiles!! how come???
}
public static void main(String[] args) {
List<List<String>> lol = null;
List<String> list = null;
probablyIllegal(lol, list); // DOES NOT COMPILE!!
// The method probablyIllegal(List<List<?>>, List<?>)
// in the type LOLUnknowns1a is not applicable for the
// arguments (List<List<String>>, List<String>)
}
}
This makes no sense! Here we aren't even trying to use two different types, and it doesn't compile! Making it a List<List<Integer>> lol
and List<String> list
also gives a similar compilation error! In fact, from my experimentation, the only way that the code compiles is if the first argument is an explicit null
type (as seen on ideone.com):
import java.util.*;
public class LOLUnknowns1b {
static void probablyIllegal(List<List<?>> lol, List<?> list) {
lol.add(list); // this compiles!! how come???
}
public static void main(String[] args) {
List<String> list = null;
probablyIllegal(null, list); // compiles fine!
// throws NullPointerException at run-time
}
}
So the questions are, with regards to LOLUnknowns1
, LOLUnknowns1a
and LOLUnknowns1b
:
- What types of arguments does
probablyIllegal
accept? - Should
lol.add(list);
compile at all? Is it typesafe? - Is this a compiler bug or am I misunderstanding the capture conversion rules for wildcards?
Appendix A: Double LOL?
In case anyone is curious, this compiles fine (as seen on ideone.com):
import java.util.*;
public class DoubleLOL {
static void omg2xLOL(List<List<?>> lol1, List<List<?>> lol2) {
// compiles just fine!!!
lol1.addAll(lol2);
lol2.addAll(lol1);
}
}
Appendix B: Nested wildcards -- what do they really mean???
Further investigation indicates that perhaps multiple wildcards has nothing to do with the problem, but rather a nested wildcard is the source of the confusion.
import java.util.*;
public class IntoTheWild {
public static void main(String[] args) {
List<?> list = new ArrayList<String>(); // compiles fine!
List<List<?>> lol = new ArrayList<List<String>>(); // DOES NOT COMPILE!!!
// Type mismatch: cannot convert from
// ArrayList<List<String>> to List<List<?>>
}
}
So it looks perhaps a List<List<String>>
is not a List<List<?>>
. In fact, while any List<E>
is a List<?>
, it doesn't look like any List<List<E>>
is a List<List<?>>
(as seen on ideone.com):
import java.util.*;
public class IntoTheWild2 {
static <E> List<?> makeItWild(List<E> list) {
return list; // compiles fine!
}
static <E> List<List<?>> makeItWildLOL(List<List<E>> lol) {
return lol; // DOES NOT COMPILE!!!
// Type mismatch: cannot convert from
// List<List<E>> to List<List<?>>
}
}
A new question arises, then: just what is a List<List<?>>
?