Сначала рассмотрим простой сценарий (см. полный источник на 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!
}
}
Две подстановочные знаки не связаны друг с другом, поэтому вы можете вызвать doNothing
с помощью List<String>
и a List<Integer>
. Другими словами, два ?
могут относиться к совершенно другим типам. Следовательно, следующее не компилируется, что следует ожидать (также на 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 ?>)
}
}
До сих пор так хорошо, но здесь, где вещи начинают очень запутываться (как видно на ideone.com):
import java.util.*;
public class LOLUnknowns1 {
static void probablyIllegal(List<List<?>> lol, List<?> list) {
lol.add(list); // this compiles!! how come???
}
}
Вышеприведенный код компилируется для меня в Eclipse и sun-jdk-1.6.0.17
на ideone.com, но должен ли он? Разве не возможно, что мы имеем List<List<Integer>> lol
и a List<String> list
, аналогичные две несвязанные ситуации с символами из TwoListsOfUnknowns
?
Фактически следующая небольшая модификация в этом направлении не компилируется, что и следовало ожидать (как видно на 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 ?>)
}
}
Итак, похоже, что компилятор выполняет свою работу, но затем мы получаем это (как видно на 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???
}
}
Опять же, мы можем иметь, например, a List<List<Integer>> lol
и a List<Float> list
, поэтому это не должно компилироваться, правильно?
На самом деле, вернемся к более простой LOLUnknowns1
(две неограниченные подстановочные знаки) и попытаемся увидеть, можем ли мы фактически вызвать probablyIllegal
каким-либо образом. Сначала попробуйте "простой" случай и выберите один тип для двух подстановочных знаков (как видно на 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>)
}
}
Это не имеет смысла! Здесь мы даже не пытаемся использовать два разных типа, и он не компилируется! Создание List<List<Integer>> lol
и List<String> list
также дает аналогичную ошибку компиляции! Фактически, из моих экспериментов единственный способ компиляции кода - это первый аргумент - явный тип null
(как видно на 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
}
}
Итак, вопросы касаются LOLUnknowns1
, LOLUnknowns1a
и LOLUnknowns1b
:
- Какие типы аргументов принимают
probablyIllegal
? - Должен ли
lol.add(list);
компилироваться вообще? Это типично? - Является ли это ошибкой компилятора или я неправильно понимаю правила преобразования захвата для подстановочных знаков?
Приложение A: Двойной LOL?
Если кому-то интересно, это компилируется отлично (как видно на 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);
}
}
Приложение B: Вложенные подстановочные знаки - что они на самом деле означают?
Дальнейшее исследование указывает на то, что, возможно, несколько подстановочных знаков не имеют ничего общего с проблемой, но скорее всего вложенные подстановочные знаки являются источником путаницы.
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<?>>
}
}
Итак, похоже, что List<List<String>>
не является List<List<?>>
. Фактически, в то время как любой List<E>
является List<?>
, он не выглядит как List<List<E>>
является List<List<?>>
(как видно на ideone. ком):
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<?>>
}
}
Возникает новый вопрос: то, что есть List<List<?>>
?