Давайте сначала рассмотрим простой сценарий ( см. Полный исходный код на ideone.com ):
import java.util.*;
public class TwoListsOfUnknowns {
static void doNothing(List> list1, List> list2) { }
public static void main(String[] args) {
List list1 = null;
List list2 = null;
doNothing(list1, list2); // compiles fine!
}
}
Два подстановочных символа не связаны между собой, поэтому вы можете вызвать Ничего не делать
со списком List
и списком List
. Другими словами, два ?
могут относиться к совершенно разным типам. Следовательно, следующее не компилируется, чего и следовало ожидать ( также на 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 is not applicable for
// the arguments (List)
}
}
Пока все хорошо, но здесь все начинает сильно запутываться (, как видно на ideone.com ):
import java.util.*;
public class LOLUnknowns1 {
static void probablyIllegal(List> lol, List> list) {
lol.add(list); // this compiles!! how come???
}
}
Приведенный выше код компилируется для меня в Eclipse и на sun-jdk-1.6.0.17
на ideone.com, но нужно ли? Возможно ли, что у нас есть List
и > lol
List
, аналогичные ситуации с двумя несвязанными подстановочными знаками из TwoListsOfUnknowns
?
На самом деле следующая небольшая модификация в этом направлении не компилируется, чего и следовало ожидать (, как видно на ideone. com ):
import java.util.*;
public class LOLUnknowns2 {
static void rightfullyIllegal(
List> lol, List> list) {
lol.add(list); // DOES NOT COMPILE! As expected!!!
// The method add(List extends Number>) in the type
// List> is not applicable for
// the arguments (List)
}
}
Похоже, что компилятор выполняет свою работу, но затем мы получаем следующее (, как показано на ideone.com ):
import java.util.*;
public class LOLUnknowns3 {
static void probablyIllegalAgain(
List> lol, List extends Number> list) {
lol.add(list); // compiles fine!!! how come???
}
}
Опять же, у нас может быть, например, List
и > lol
List
, так что это не должно компилироваться, верно?
На самом деле, давайте вернемся к более простому LOLUnknowns1
(два неограниченных символа подстановки) и попытаемся увидеть, можем ли мы каким-либо образом вызвать вероятно Illegal
. Попробуем "легкий" case first и выберите один и тот же тип для двух подстановочных знаков (, как показано на ideone.com ):
import java.util.*;
public class LOLUnknowns1a {
static void probablyIllegal(List> lol, List> list) {
lol.add(list); // this compiles!! how come???
}
public static void main(String[] args) {
List> lol = null;
List list = null;
probablyIllegal(lol, list); // DOES NOT COMPILE!!
// The method probablyIllegal(List>, List>)
// in the type LOLUnknowns1a is not applicable for the
// arguments (List>, List)
}
}
Это не имеет смысла! Здесь мы даже не пытаемся использовать два разных типа, и он не компилируется! Создание списка List
и > lol
List
также дает аналогичную ошибку компиляции! Фактически, согласно моим экспериментам, код компилируется только в том случае, если первый аргумент является явным нулевым
типом (, как показано на ideone.com ):
import java.util.*;
public class LOLUnknowns1b {
static void probablyIllegal(List> lol, List> list) {
lol.add(list); // this compiles!! how come???
}
public static void main(String[] args) {
List list = null;
probablyIllegal(null, list); // compiles fine!
// throws NullPointerException at run-time
}
}
Итак, вопросы касательно LOLUnknowns1
, LOLUnknowns1a
и LOLUnknowns1b
:
, вероятно, Illegal
? lol.add (список);
компилировать вообще? Это типично? Если кому-то интересно, это хорошо компилируется (, как показано на ideone.com ):
import java.util.*;
public class DoubleLOL {
static void omg2xLOL(List> lol1, List> lol2) {
// compiles just fine!!!
lol1.addAll(lol2);
lol2.addAll(lol1);
}
}
Дальнейшее исследование показывает, что, возможно, несколько подстановочных знаков не имеют ничего общего с проблемой, а скорее вложенный подстановочный знак является источником путаницы.
import java.util.*;
public class IntoTheWild {
public static void main(String[] args) {
List> list = new ArrayList(); // compiles fine!
List> lol = new ArrayList>(); // DOES NOT COMPILE!!!
// Type mismatch: cannot convert from
// ArrayList> to List>
}
}
Таким образом, похоже, что List
не является >
List
. Фактически, хотя любой >
List
является List >
, не похоже, что какой-либо List
является >
Список >
( на ideone.com ):
import java.util.*;
public class IntoTheWild2 {
static List> makeItWild(List list) {
return list; // compiles fine!
}
static List> makeItWildLOL(List> lol) {
return lol; // DOES NOT COMPILE!!!
// Type mismatch: cannot convert from
// List> to List>
}
}
Тогда возникает новый вопрос: что такое List
? >
Как указано в Приложении B, это не имеет ничего общего с несколько подстановочных знаков, а скорее неправильное понимание того, что на самом деле означает List
. >
Давайте сначала напомним себе, что означает инвариантность дженериков Java:
Integer
- это Number
List
is ] НЕ a Список
Список
IS a Список extends Number>
Теперь мы просто применяем тот же аргумент к ситуации с нашим вложенным списком (см. приложение для более подробной информации) :
List
is (захватывается ) a List >
List >
is НЕ (захватывается) a List >
List >
IS (захватывается) a List extends List >>
С таким пониманием можно объяснить все фрагменты вопроса. Путаница возникает из-за (ложного) предположения, что такой тип, как List
, может захватывать такие типы, как >
List
, >
List
и т. Д. Это НЕ истина. >
То есть List
: >
Список extends List >>
Вот фрагмент, иллюстрирующий вышеуказанные моменты:
List<List<?>> lolAny = new ArrayList<List<?>>();
lolAny.add(new ArrayList<Integer>());
lolAny.add(new ArrayList<String>());
// lolAny = new ArrayList<List<String>>(); // DOES NOT COMPILE!!
List<? extends List<?>> lolSome;
lolSome = new ArrayList<List<String>>();
lolSome = new ArrayList<List<Integer>>();
Вот еще один пример с ограниченным вложенным подстановочным знаком:
List<List<? extends Number>> lolAnyNum = new ArrayList<List<? extends Number>>();
lolAnyNum.add(new ArrayList<Integer>());
lolAnyNum.add(new ArrayList<Float>());
// lolAnyNum.add(new ArrayList<String>()); // DOES NOT COMPILE!!
// lolAnyNum = new ArrayList<List<Integer>>(); // DOES NOT COMPILE!!
List<? extends List<? extends Number>> lolSomeNum;
lolSomeNum = new ArrayList<List<Integer>>();
lolSomeNum = new ArrayList<List<Float>>();
// lolSomeNum = new ArrayList<List<String>>(); // DOES NOT COMPILE!!
Чтобы вернуться к фрагментам в вопрос, следующее ведет себя так, как ожидалось (, как видно на ideone.com ):
public class LOLUnknowns1d {
static void nowDefinitelyIllegal(List<? extends List<?>> lol, List<?> list) {
lol.add(list); // DOES NOT COMPILE!!!
// The method add(capture#1-of ? extends List<?>) in the
// type List<capture#1-of ? extends List<?>> is not
// applicable for the arguments (List<capture#3-of ?>)
}
public static void main(String[] args) {
List<Object> list = null;
List<List<String>> lolString = null;
List<List<Integer>> lolInteger = null;
// these casts are valid
nowDefinitelyIllegal(lolString, list);
nowDefinitelyIllegal(lolInteger, list);
}
}
lol.add (list);
недопустимо, потому что у нас может быть List
и список > lol
List
. Фактически, если мы закомментируем некорректное утверждение, код компилируется, и это именно то, что мы имеем при первом вызове в main
.
Все вероятно незаконные
методы в вопросе не являются незаконными. Все они совершенно законны и безопасны. В компиляторе нет абсолютно никакой ошибки. Он делает именно то, что должен делать.
List animals = new ArrayList ()
? (Это было поднято в первой редакции ответа; это достойное дополнение к аргументу инварианта типа.)
5.1.10 Преобразование захвата
Пусть G назовет объявление универсального типа с n параметрами формального типа A 1 … A n с соответствующими границами U 1 … U n . Существует преобразование захвата из G
1 … T n > в G1 … S ] n >, где для 1 <= i <= n :
- Если T i - аргумент типа подстановочного знака в форме
?
тогда…- Если T i является аргументом типа подстановочного знака формы
? extends
B i , тогда…- Если T i является аргументом типа подстановочного знака формы
? super
B i , затем…- В противном случае S i = T i .
Преобразование захвата не применяется рекурсивно.
Этот раздел может сбивать с толку, особенно в отношении нерекурсивного применения преобразования захвата (здесь CC ), но суть в том, что не все ?
может CC; это зависит от того, где оно появляется . В правиле 4 нет рекурсивного применения, но когда применяются правила 2 или 3, то соответствующий B i сам может быть результатом CC.
Давайте рассмотрим несколько простых примеров:
List >
can CC List
?
can CC by rule 1 List extends Number>
can CC List
?
может CC по правилу 2 Число
Список extends Number>
can NOT CC List
?
может CC по правилу 2, но ошибка времени компиляции возникает из-за несовместимых типов Теперь давайте попробуем вложиться:
List >
может НЕ CC List >
?
может НЕ CC Список extends List >>
can CC List >
?
может CC по правилу 2 List >
, который может CC List
?
могут CC List расширяет список extends Number >>
can CC List >
?
может CC по правилу 2 расширяет Number>
, который может CC List
?
могут CC List расширяет список extends Number >>
can NOT CC List >
?
может CC по правилу 2 расширяет Number>
, который может CC, но дает ошибку времени компиляции при применении к List
?
can CC Для дополнительной иллюстрации, почему некоторые ]?
может CC и другие не могут, примите во внимание следующее правило: вы можете НЕ напрямую создать экземпляр типа подстановочного знака. То есть следующее дает ошибку времени компиляции:
// WildSnippet1
new HashMap<?,?>(); // DOES NOT COMPILE!!!
new HashMap<List<?>, ?>(); // DOES NOT COMPILE!!!
new HashMap<?, Set<?>>(); // DOES NOT COMPILE!!!
Однако следующее компилируется нормально:
// WildSnippet2
new HashMap<List<?>,Set<?>>(); // compiles fine!
new HashMap<Map<?,?>, Map<?,Map<?,?>>>(); // compiles fine!
Причина компиляции WildSnippet2
заключается в том, что, как объяснялось выше, нет ни одного из ?
банка CC. В WildSnippet1
либо K
, либо V
(или оба) из HashMap
can CC, что делает прямое создание через новое
незаконно.
Никакой аргумент с обобщенными типами не может быть принят. В случае LOLUnknowns1b
null
принимается, как если бы первый аргумент был набран как List
. Например, это компилируется:
List lol = null;
Список list = null;
вероятноIllegal (смеется, список);
IMHO lol.add (list);
не должен даже компилироваться, но поскольку lol.add ()
нуждается в аргументе типа List >
и поскольку список соответствует List >
, он работает.
Странный пример, который заставляет меня думать об этой теории:
static void вероятноIllegalAgain (List > lol, List Extends Integer> list) {
lol.add (список); // компилируется нормально !!! почему???
}
lol.add ()
требуется аргумент типа List расширяет Number>
, а список набирается как List extends Integer>
, он подходит. Он не будет работать, если он не совпадает.
То же самое для двойного LOL и других вложенных подстановочных знаков , пока первый захват соответствует второму, все в порядке (и не может быть).
Опять же, я не уверен, но это действительно похоже на ошибку.
Я рад, что не единственный, кто постоянно использует переменные lol
.
Ресурсы:
http://www.angelikalanger.com , FAQ по дженерикам
РЕДАКТИРОВАНИЯ:
не эксперт, но я думаю, что могу это понять.
давайте изменим ваш пример на что-то эквивалентное, но с более различающимися типами:
static void probablyIllegal(List<Class<?>> x, Class<?> y) {
x.add(y); // this compiles!! how come???
}
давайте изменим List на [], чтобы быть более понятным:
static void probablyIllegal(Class<?>[] x, Class<?> y) {
x.add(y); // this compiles!! how come???
}
теперь x - это , а не массив из ] некоторый тип класса. это массив любого типа класса. он может содержать Class
и a Class
. это не может быть выражено с помощью обычного параметра типа:
static<T> void probablyIllegal(Class<T>[] x //homogeneous! not the same!
Class >
является супертипом Class
для any T
. Если мы думаем, что тип представляет собой набор объектов , набор Class >
является объединением всех наборов из Класс
для всех T
. (включает ли он его сам? Не знаю ...)