Java Generics: назначение с вложенными подстановочными параметрами

Для следующего примера кода:

public static class Abc<X> { }
public static class Def<Y> { }
public static class Ghi<Z> { }

public void doThis() {
    List<?> listOne;
    List<Abc<?>> listTwo;
    List<Abc<Def<?>>> listThree;
    List<Abc<Def<Ghi<?>>>> listFour;
    List<Abc<Def<Ghi<String>>>> listFive;

    Abc<Def<Ghi<String>>> abcdef;

    abcdef = new Abc<Def<Ghi<String>>>();

    listOne.add(abcdef);    // line 1
    listTwo.add(abcdef);    // line 2
    listThree.add(abcdef);  // line 3
    listFour.add(abcdef);   // line 4
    listFive.add(abcdef);   // line 5
}

Строки 1, 3 и 4 не компилируются:

(строка 1)

The method add(capture#1-of ?) in the type List<capture#1-of ?> is not applicable for the arguments (Abc<Def<Ghi<String>>>)

(строка 3)

The method add(Abc<Def<?>>) in the type List<Abc<Def<?>>> is not applicable for the arguments (Abc<Def<Ghi<String>>>)

(строка 4)

The method add(Abc<Def<Ghi<?>>>) in the type List<Abc<Def<Ghi<?>>>> is not applicable for the arguments (Abc<Def<Ghi<String>>>)

Однако строки 2 и 5 компилируются.

Кто-нибудь может объяснить, почему строки 1, 3 и 4 не являются законными назначениями? И если подстановочные знаки нельзя использовать таким образом в этих строках, то почему назначение в строке 2 допустимо?


person sumitsu    schedule 19.06.2014    source источник
comment
Чрезвычайно хороший вопрос! Для строки 1 это связано с тем, что вы не можете добавить ничего в список неизвестного типа, кроме null.   -  person rlegendi    schedule 19.06.2014
comment
связанные...   -  person Eugene    schedule 30.10.2019


Ответы (1)


listOne.add(abcdef) (строка 1) недействителен, поскольку List<?> представляет собой список неизвестного определенного типа. Например, это может быть List<String>, поэтому мы не хотим добавлять ничего, кроме String. Ошибка компилятора возникает из-за того, что Abc<Def<Ghi<String>>> нельзя присвоить ?.

listTwo.add(abcdef) (строка 2) допустим, поскольку List<Abc<?>> представляет собой список Abc любого типа. Верно: вложенные подстановочные знаки отличаются от подстановочных знаков верхнего уровня тем, что они представляют любой тип, а не конкретный тип (другими словами, вложенные подстановочные знаки не захват). Компилятор разрешает это, потому что Abc<Def<Ghi<String>>> можно присвоить Abc<?>. См. этот пост для дальнейшего обсуждения вложенных подстановочных знаков: Несколько подстановочных знаков в универсальных методах сильно смущают компилятор Java (и меня!)

listThree.add(abcdef) (строка 3) недействителен, поскольку List<Abc<Def<?>>> представляет собой список Abcs из Defs любого типа. Обобщения не являются ковариантными, поэтому Abc<Def<Ghi<String>>> нельзя присвоить Abc<Def<?>>, хотя Def<Ghi<String>> можно присвоить Def<?>. List<Integer> нельзя присвоить List<Number> по той же причине. См. этот пост для дальнейшего объяснения: List‹Dog› подкласс List‹Animal›? Почему дженерики Java не являются неявно полиморфными?

listFour.add(abcdef) (строка 4) недействителен по той же причине - Abc<Def<Ghi<String>>> нельзя присвоить Abc<Def<Ghi<?>>>.

listFive.add(abcdef) (строка 5) действителен, потому что универсальные типы точно совпадают — Abc<Def<Ghi<String>>>, очевидно, можно присвоить Abc<Def<Ghi<String>>>.

person Paul Bellora    schedule 19.06.2014
comment
В дополнение хотел отметить, что проблемы присваиваемости строк 3 и 4 можно решить, изменив объявления с List<Abc<Def<?>>> listThree на List<Abc<? extends Def<?>>> listThree и с List<Abc<Def<Ghi<?>>>> на List<Abc<? extends Def<? extends Ghi<?>>>> listFour. Спасибо за краткое объяснение и полезные ссылки; теперь намного яснее. - person sumitsu; 19.06.2014
comment
@PaulBellora Верно, вложенные подстановочные знаки отличаются от подстановочных знаков верхнего уровня тем, что они представляют любой тип, а не какой-то конкретный тип это! очень это! - person Eugene; 25.10.2019