Почему я не могу добавить коллекцию <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>> и т.д.