Суть вопроса в том, почему это вызывает ошибку времени компиляции?

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<?>>.

16
yshavit 22 Май 2015 в 01:47
2
Связано: stackoverflow.com/questions/26766704 /… stackoverflow.com/ questions / 30090242 /… (Но я не думаю, что вы спрашиваете здесь о спецификации, верно? В любом случае, я не думаю, что есть практическая причина, по которой преобразование запрещено лично.)
 – 
Radiodef
22 Май 2015 в 01:57
Спасибо за ссылки @Radiodef. Но да, я спрашиваю не столько о спецификации, сколько о теории - действительно ли текущее положение вещей дает нам некоторую защиту или это просто крайний случай, который система типов не покрывает.
 – 
yshavit
22 Май 2015 в 02:11
1
На самом деле ваш вопрос сводится к тому, "почему Collection<?> не содержит Collection?" (в смысле сдерживания JLS). Нам нужно будет покопаться в обсуждениях авторов JLS, чтобы получить реальный ответ об их намерениях. Однако я буду размышлять над ответом: потому что текущие правила содержания легче реализовать без особых исключений, подобных этому. Другими словами, нет причин, по которым соглашение не может быть изменено, это будет просто специальное исключение в спецификации языка, которое пытается избежать особых исключений. Но это только предположение.
 – 
sprinter
22 Май 2015 в 02:38
Ну, и как я немного указал в последней из моих ссылок выше, он играет забавную игру с системой типов. Class является супертипом Class<?>, но List<Class> будет подтипом List<Class<?>>.
 – 
Radiodef
22 Май 2015 в 02:40
Да согласился. JLS неплохо справляется с задачей в большинстве случаев избегать подобных забавных игр. В этом случае спецификация может быть изменена (с соответствующими усилиями для устройств, но без большой опасности для кодеров), но, учитывая, что есть другие решения всех проблем, которые она может решить, зачем увеличивать сложность с небольшой выгодой?
 – 
sprinter
22 Май 2015 в 02:43

1 ответ

Лучший ответ

Во-первых, необработанный тип и тип подстановки совершенно разные. Во-первых, необработанный тип полностью стирает всю общую информацию.

Итак, у нас есть List<x> и List<y>, где x не y. Это определенно не отношения подтипов.

Тем не менее, вы можете попросить разрешить кастинг. Но, пожалуйста, прочтите JLS 5.5.1 , и скажите мне, что вы хотите добавить к нему что-то еще :) Просмотрите всю страницу, на самом деле, это отличная стена текста только для трансляции .

И помните, что это только первая волна всего эффекта. А как насчет List<List<x>>, List<List<y>> и т. Д.

2
yshavit 22 Май 2015 в 21:20
Не уверен, что согласен с тем, что усложнение спецификации - проблема. Посмотрите на главу 18. Это все новенькое. Но эффект ряби реален. Конечно, все это можно исправить (и спецификацию упростить ...), полностью удалив необработанные типы.
 – 
Radiodef
22 Май 2015 в 03:10
- не проблема? Вам легко сказать :) Глава 18 - современное чудо, удивительно, что это сделано, и в основном одним человеком. Наверное, это понимали только 3 парня и 1 девушка; и я уверен, что они больше не делают этого после того, как закончили кодирование компиляторов, LOL.
 – 
ZhongYu
22 Май 2015 в 03:13
Я также не уверен, что делать спецификацию немного более сложной, если это делает язык, возможно, проще , - это ужасная вещь. Но упомянутый вами эффект пульсации - хороший контраргумент тому, что это на самом деле упрощает язык.
 – 
yshavit
22 Май 2015 в 21:21