Давайте сначала рассмотрим простой сценарий ( см. Полный источник на 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>
и 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
и 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???
}
}
Опять же, мы можем иметь, например, List<List<Integer>> lol
и 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: Double 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.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<?>>
}
}
Тогда возникает новый вопрос: что такое List<List<?>>
?