Несколько подстановочных знаков в универсальных методах делают Java-компилятор (и меня!) Очень запутанным
Давайте сначала рассмотрим простой сценарий (см полный источник на 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: Двойной 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<?>>
?