Не удается преобразовать из списка<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 ≤ in) является типом, все следующие:

  • C<S1,...,Sn>, где Si содержит Ti (1 ≤ in).

Это относится к тому, что 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<?>> может быть доказано с транзитивностью:

    1. ? extends List содержит ? extends List<?>, так как List это супертип List<?>,

    2. ? extends List<?> содержит List<?>,

    3. Следовательно ? 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) Предупреждение показывает, что необработанные типы обходят проверки универсального типа, откладывая перехват небезопасного кода во время выполнения. Поэтому вам следует избегать использования необработанных типов.

Другие вопросы по тегам