Не удается преобразовать из списка<List> в список<List <? >>
Необработанный список преобразуется в List<?>
просто хорошо. Почему список необработанных списков нельзя преобразовать в список List<?>
?
{ // works
List raw = null;
List<?> wild = raw;
}
{ // Type mismatch: cannot convert from List<List> to List<List<?>>
List<List> raw = null;
List<List<?>> wild = raw;
}
Предыстория (для смягчения проблемы xy):
API, который я использую, возвращает List<JAXBElement>
, Я знаю, что это всегда List<JAXBElement<String>>
, Я планирую зациклить и построить свой собственный List<String>
, но я пытался исправить (но не подавить) предупреждение компилятора необработанного типа, когда я пишу List<JAXBElement> raw = api();
,
Я старался:
List<JAXBElement<?>> raw = api();
List<JAXBElement<?>> raw = (List<JAXBElement<?>>) api();
но они дают ошибку несоответствия типов.
Интересно, что это не дает предупреждения или ошибки:
for (JAXBElement<?> e : api()) {
// ...
}
3 ответа
// #1 (does compile)
List raw = null;
List<?> wild = raw;
// #2 (doesn't compile)
List<List> raw = null;
List<List<?>> wild = raw;
Сначала давайте разберемся, почему это на самом деле не связанные задания. То есть они руководствуются разными правилами.
# 1 называется непроверенным преобразованием:
Существует неконтролируемое преобразование из необработанного класса или типа интерфейса ( §4.8)
G
к любому параметризованному типу формыG<T1,...,Tn>
,
В частности, это особый случай контекста присваивания только для этого сценария:
Если после того, как были применены [другие возможные преобразования], результирующий тип является необработанным типом, может быть применено непроверенное преобразование.
# 2 требует преобразования ссылочного типа; однако проблема заключается в том, что это не расширяющееся преобразование (это тот тип ссылочного преобразования, который неявно разрешается без преобразования).
Это почему? Ну, это определенно регулируется правилами общих подтипов и, более конкретно, этот пункт:
Учитывая объявление общего типа
C<F1,...,Fn>
(n > 0), прямые супертипы параметризованного типаC<T1,...,Tn>
, гдеTi
(1 ≤ i ≤ n) является типом, все следующие:
C<S1,...,Sn>
, гдеSi
содержитTi
(1 ≤ i ≤ n).
Это относится к тому, что JLS вызывает сдерживание, где для правильного присваивания аргументы левой части должны содержать аргументы правой части. Сдерживание в значительной степени определяет общие подтипы, поскольку "конкретные" общие типы являются инвариантными.
Вы можете быть знакомы с идеями, которые:
-
List<Dog>
это неList<Animal>
- но
List<Dog>
этоList<? extends Animal>
,
Ну, последнее верно, потому что ? extends Animal
содержит Dog
,
Таким образом, вопрос становится "аргумент типа List<?>
содержать необработанный аргумент типа 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. avoids a raw type warning
@SuppressWarnings("unchecked")
List<List<?>> list2 = (List<List<?>>) (List<? super List<?>>) api();
(Вы можете заменить JAXBElement
для внутреннего List
.)
Ваш вариант использования для этого кастинга должен быть безопасным, потому что List<List<?>>
является более ограничительным типом, чем List<List>
,
Необработанный оператор типа представляет собой расширение, а затем непроверенное присваивание. Это работает, потому что, как показано выше, любой параметризованный тип может быть преобразован в его необработанный тип и наоборот.
Несколько более безопасный оператор (названный таковым, потому что он теряет меньше информации о типе) - это расширение приведения, а не приведение. Это работает путем приведения к общему супертипу:
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<?>>
.)Третий пример работает аналогично второму примеру, приводя к общему супертипу
List<? super List<?>>
, Поскольку он не использует необработанный тип, мы можем исключить еще одно предупреждение.
Нетехническое резюме здесь заключается в том, что спецификация подразумевает, что нет ни подтипа, ни отношения супертипа между List<List>
а также List<List<?>>
,
Хотя преобразование из List<List>
в List<List<?>>
должно быть безопасно, это не разрешено. (Это безопасно, потому что оба List
который может хранить любой вид List
, но List<List<?>>
налагает больше ограничений на то, как его элементы могут быть использованы после их извлечения.)
К сожалению, нет практической причины, по которой он не компилируется, за исключением того, что необработанные типы являются странными и использование их проблематично.
Вы не можете назначить или разыграть это напрямую, потому что необработанный тип List
не то же самое, что List<?>
,
Когда используешь List
проверка типов игнорируется, и вы можете использовать любой универсальный метод с любым типом. Когда используешь List<?>
компилятор не позволит вам использовать методы с общими параметрами.
Поэтому вы можете игнорировать предупреждения:
@SuppressWarnings("rawtypes")
И / или приведите его явно с помощью обходного пути:
List<JAXBElement<String>> raw = (List<JAXBElement<String>>) ((Object)api());
Если вы хотите удалить только предупреждения, вы можете использовать @SuppressWarnings("rawtypes").
В основном проблема заключается в том, что компилятор обрабатывает rawtype как примитивный объект предыдущих обобщений, поэтому... "старый объект" не является "универсальным объектом", поэтому... вы не можете их приводить.
Прочитайте это из официального документа: http://docs.oracle.com/javase/tutorial/java/generics/rawTypes.html
Но если вы назначите необработанный тип параметризованному типу, вы получите предупреждение:
Box rawBox = new Box (); // rawBox - это необработанный тип Box Box intBox = rawBox; // предупреждение: непроверенное преобразование Вы также получите предупреждение, если используете необработанный тип для вызова универсальных методов, определенных в соответствующем универсальном типе:
Box stringBox = new Box<> (); Box rawBox = stringBox; rawBox.set (8); // warning: unchecked invocation to set (T) Предупреждение показывает, что необработанные типы обходят проверки универсального типа, откладывая перехват небезопасного кода во время выполнения. Поэтому вам следует избегать использования необработанных типов.