Подтвердить что ты не робот

Невозможно преобразовать из списка <список> в список <список <? >>

Необработанный список преобразуется в 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;
}

Backstory (для уменьшения проблемы 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()) {
    // ...
}
4b9b3361

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

К сожалению, нет практической причины, из-за которой невозможно выполнить компиляцию, за исключением того, что исходные типы странны, а их использование проблематично.

Ответ 2

Вы не можете назначить или применить его напрямую, потому что тип raw List не совпадает с List<?>.

При использовании List проверка типов игнорируется, и вы можете использовать любой общий метод с любым типом. При использовании List<?> компилятор не позволит вам использовать методы с общими параметрами.


Поэтому вы можете либо игнорировать предупреждения:

@SuppressWarnings("rawtypes")

И/Или явно указать его с помощью обходного пути:

List<JAXBElement<String>> raw = (List<JAXBElement<String>>) ((Object)api());

Ответ 3

Если вы хотите только удалить предупреждения, вы можете использовать @SuppressWarnings ( "rawtypes" ).

В основном проблема заключается в том, что компилятор рассматривает rawtype как примитивный предыдущий дженерик объекта, поэтому... "старый объект" не является "универсальным объектом", поэтому... вы не можете их отличать.

Прочтите это из официального документа: http://docs.oracle.com/javase/tutorial/java/generics/rawTypes.html

Но если вы присвоите тип raw параметризованному типу, вы получите предупреждение:

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