Несколько подстановочных знаков на общих методах делает компилятор 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>>();
аргумент С дженерики должны быть приняты. В случае
LOLUnknowns1bthenullпринимается, как если бы первый аргумент был набран как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. (включает ли он себя сам? Я не знаю...)