Несколько подстановочных знаков на общих методах делает компилятор 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
in 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<?>>
?
3 ответа:
как указано в приложении B, это не имеет ничего общего с несколькими подстановочными знаками, а скорее, непонимание того, что
List<List<?>>
на самом деле означает.давайте сначала напомним себе, что это значит, что генераторы Java инвариантны:
- Теги
Integer
этоNumber
- A
List<Integer>
и не aList<Number>
- A
List<Integer>
и aList<? extends Number>
теперь мы просто применяем тот же аргумент к нашему вложенный список ситуация (Подробнее см. Приложение):
- A
List<String>
is (captureable by) aList<?>
- A
List<List<String>>
и не (captureable by) aList<List<?>>
- A
List<List<String>>
и (captureable by) aList<? extends List<?>>
с этим пониманием можно объяснить все фрагменты в вопросе. Путаница возникает в (ложно) полагая, что тип, как
List<List<?>>
может захват типов, таких какList<List<String>>
,List<List<Integer>>
, etc. Это не правда.то есть, a
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>>();
аргумент С дженерики должны быть приняты. В случае
LOLUnknowns1b
thenull
принимается, как если бы первый аргумент был набран какList
. Например, это делает компиляцию:List lol = null; List<String> list = null; probablyIllegal(lol, list);
ИМХО
lol.add(list);
не должен даже компилироваться, но какlol.add()
нужен аргумент типаList<?>
и как список помещается вList<?>
это работает.
Странный пример, который заставляет меня думать об этой теории: :static void probablyIllegalAgain(List<List<? extends Number>> lol, List<? extends Integer> list) { lol.add(list); // compiles fine!!! how come??? }
lol.add()
нужен аргумент типаList<? extends Number>
и список набирается какList<? extends Integer>
, это подходит. Он не будет работать, если он не соответствует. То же самое для двойного LOL и других вложенных подстановочных знаков,пока первый захват соответствует второму, все в порядке (и не быть).опять же, я не уверен, но это действительно похоже на баг.
Я рад, что не только один, чтобы использовать
lol
переменные все время.ресурсы :
http://www.angelikalanger.com, FAQ о дженерикахизменения :
- добавлен комментарий о двойном Lol
- и вложенные шаблоны.
не эксперт, но я думаю, что могу это понять.
Давайте изменим ваш пример на что-то эквивалентное, но с более отличительными типами:
static void probablyIllegal(List<Class<?>> x, Class<?> y) { x.add(y); // this compiles!! how come??? }
Давайте изменим список на [], чтобы быть более освещающим:
static void probablyIllegal(Class<?>[] x, Class<?> y) { x.add(y); // this compiles!! how come??? }
теперь x-это не массив какой-то тип класса. это массив любой тип класса. он может содержать
Class<String>
и aClass<Int>
. это не может быть выражено с обычным параметром типа:static<T> void probablyIllegal(Class<T>[] x //homogeneous! not the same!
Class<?>
Это супер типClass<T>
на любойT
. Если мы думаем тип это набор объектов, setClass<?>
на наборы наClass<T>
для всехT
. (включает ли он себя сам? Я не знаю...)