Почему я не могу добавить коллекцию <GenericFoo> в коллекцию <GenericFoo <? >>
Суть вопроса в том, почему это вызывает ошибку времени компиляции?
List<Collection> raws = new ArrayList<Collection>();
List<Collection<?>> c = raws; // error
Фон
Я понимаю, почему дженерики вообще не ковариантны. Если бы мы могли назначить List<Integer>
на List<Number>
, мы будем подвергать себя ClassCastExceptions:
List<Integer> ints = new ArrayList<Integer>();
List<Number> nums = ints; // compile-time error
nums.add(Double.valueOf(1.2));
Integer i = ints.get(0); // ClassCastException
Мы получаем ошибку времени компиляции в строке 2, чтобы избавить нас от ошибки времени выполнения в строке 4. Это имеет смысл.
List<C>
до List<C<?>>
Но как насчет этого:
List<Collection> rawLists = new ArrayList<Collection>();
List<Collection<?>> wildLists = rawLists; // compile-time error
// scenario 1: add to raw and get from wild
rawLists.add(new ArrayList<Integer>());
Collection<?> c1 = wildLists.get(0);
Object o1 = c1.iterator().next();
// scenario 2: add to wild and get from raw
wildLists.add(new ArrayList<String>());
Collection c2 = rawLists.get(0);
Object o2 = c2.iterator().next();
В обоих сценариях, в конечном итоге, я получаю только элементы Object
без кастинга, поэтому я не могу получить "таинственное" исключение ClassCastException.
Раздел в JLS, который соответствует этому, §4.10.2, поэтому я понимаю, почему компилятор дает мне ошибку; я не понимаю, почему спецификация была написана таким образом и (чтобы предотвратить спекулятивные/основанные на мнениях ответы), действительно ли это дает мне любую безопасность во время компиляции.
Пример мотивации
Если вам интересно, здесь (урезанная версия) вариант использования:
public Collection<T> readJsons(List<String> jsons, Class<T> clazz) {
List<T> list = new ArrayList<T>();
for (String json : jsons) {
T elem = jsonMapper.readAs(json, clazz);
list.add(elem);
}
return list;
}
// call site
List<GenericFoo<?>> foos = readJsons(GenericFoo.class); // error
Ошибка связана с тем, что GenericFoo.class
имеет тип Class<GenericFoo>
, а не Class<GenericFoo<?>>
(§15.8.2). Я не уверен, почему это так, хотя я подозреваю, что это родственная причина; но независимо от того, что это не проблема, если Class<GenericFoo>
может быть запущен - неявно или явно - до Class<GenericFoo<?>>
.
Ответы
Ответ 1
Прежде всего, тип raw и подстановочный знак совершенно разные. Во-первых, сырой тип полностью уничтожает всю общую информацию.
Итак, мы имеем List<x>
и List<y>
, где x не является y. Это, конечно, не отношение подтипа.
Вы можете, тем не менее, попросить кастинг быть допущенным. Но, пожалуйста, прочитайте
JLS 5.5.1, и скажите, что вы хотите добавить что-то еще к нему:) Просмотрите всю страницу, на самом деле это отличная стена текста просто для кастинга.
И помните, что это всего лишь первая пульсация всего эффекта. Как насчет List<List<x>>
и List<List<y>>
и т.д.