Ответ 1
// #1 (does compile)
List raw = null;
List<?> wild = raw;
// #2 (doesn't compile)
List<List> raw = null;
List<List<?>> wild = raw;
Сначала давайте разобраться, почему это фактически несвязанные назначения. То есть они определяются разными правилами.
# 1 называется unchecked conversion:
Существует непроверенное преобразование из исходного класса или типа интерфейса (§4.8)
G
к любому параметризованному типу формыG<T1,...,Tn>
.
В частности, это особый случай контекст присваивания только для этого сценария:
Если после применения [других возможных преобразований] результирующий тип является необработанным, тогда может быть применено непроверенное преобразование.
# 2 требует преобразования ссылочного типа; однако проблема заключается в том, что это не расширяющееся преобразование (которое является типом преобразования ссылок, которое неявно разрешено без литья).
Почему? Ну, это специально регулируется правилами общего подтипирования и, более конкретно, этот пункт:
Учитывая объявление универсального типа
C<F1,...,Fn>
(n > 0), прямые супертипы параметризованного типаC<T1,...,Tn>
, гдеTi
(1 ≤ я ≤ n) являются типом, являются следующими:
C<S1,...,Sn>
, гдеSi
содержитTi
(1 ≤ я ≤ n).
Это относится к чему-то, что JLS вызывает сдерживание, где для правильного назначения аргументы левая часть должна содержать аргументы правой части. Сдерживание в значительной степени управляет общим подтипированием, поскольку "конкретные" общие типы являются инвариантными.
Вы можете быть знакомы с идеями, которые:
- a
List<Dog>
не являетсяList<Animal>
- но a
List<Dog>
являетсяList<? extends Animal>
.
Ну, последнее верно, потому что ? extends Animal
содержит Dog
.
Таким образом, вопрос становится "содержит ли List <? > содержащий исходный список"? И ответ - нет: хотя List<?>
является подтипом List
, это отношение не выполняется для аргументов типа.
Не существует специального правила, которое делает его истинным: List<List<?>>
не является подтипом List<List>
по существу по той же причине List<Dog>
не является подтипом List<Animal>
.
Итак, поскольку List<List>
не является подтипом List<List<?>>
, назначение недопустимо. Точно так же вы не можете выполнить прямой сужение преобразования, потому что List<List>
не является супертипом List<List<?>>
либо.
Чтобы выполнить задание, вы все равно можете применить бросок. Есть три способа сделать это, которые кажутся мне разумными.
// 1. raw type
@SuppressWarnings("unchecked")
List<List<?>> list0 = (List) api();
// 2. slightly safer
@SuppressWarnings({"unchecked", "rawtypes"})
List<List<?>> list1 = (List<List<?>>) (List<? extends List>) api();
// 3. added 5/23/16: this version doesn't cause a raw type warning
@SuppressWarnings("unchecked")
List<List<?>> list2 = (List<List<?>>) (List<? super List<?>>) api();
(Вы можете заменить JAXBElement
на внутренний List
.)
Ваш прецедент для этого литья должен быть безопасным, потому что List<List<?>>
является более ограничительным типом, чем List<List>
.
-
Оператор типа raw - это расширение, отличное от выбранного назначения. Это работает, потому что, как показано выше, любой параметризованный тип может быть преобразован в его необработанный тип и наоборот.
-
Несколько более безопасное утверждение (получившее название так, потому что оно теряет меньше информации о типе) - это расширяющийся литье, а затем сужение приведения. Это работает путем литья в общий супертип:
List<? extends List> ╱ ╲ List<List<?>> List<List>
Ограниченный подстановочный знак позволяет рассматривать аргументы типа для подтипирования через сдерживание.
И как побочная заметка, в предыдущих версиях этого ответа я колебался в том, почему
List<? extends List>
считается супертипомList<List<?>>
, но это можно доказать через транзитивное свойство:-
? extends List
содержит? extends List<?>
, потому чтоList
является супертипомList<?>
. -
? extends List<?>
содержитList<?>
. -
Поэтому
? extends List
содержитList<?>
.
То есть
List<? extends List> :> List<? extends List<?>> :> List<List<?>>
. -
-
Третий пример (добавленный 5/23/16) работает почти так же, как второй пример, путем литья в общий супертип
List<? super List<?>>
. Это немного лучше в том, что, поскольку он не использует сырой тип, мы можем подавить еще одно предупреждение.
Нетехническое резюме здесь состоит в том, что спецификация подразумевает, что между List<List>
и List<List<?>>
нет отношения подтипа и супертипа.
Хотя преобразование с List<List>
в List<List<?>>
должно быть безопасным, оно недопустимо. (Это безопасно, потому что оба являются List
, которые могут хранить любой тип List
, но List<List<?>>
накладывает больше ограничений на то, как его элементы могут быть использованы после их получения.)
К сожалению, нет практической причины, из-за которой невозможно выполнить компиляцию, за исключением того, что исходные типы странны, а их использование проблематично.