Java Generics: assignment with nested wildcard parameters
Asked Answered
K

1

12

For the following code sample:

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
}

Lines 1, 3, and 4 do not compile:

(line 1)

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

(line 3)

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

(line 4)

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

Lines 2 and 5, however, compile.

Could anyone explain why lines 1, 3, and 4 are not legal assignments? And if wildcard parameters cannot be used in that way on those lines, then why is the assignment on line 2 legal?

Krishna answered 19/6, 2014 at 13:57 Comment(2)
Extremely good question! For line 1, it is because you cannot add anything to a list of unknown type except null.Reuter
related...Proconsul
B
12

listOne.add(abcdef) (line 1) is invalid because List<?> represents a list of some unknown specific type. For example, it could be a List<String>, so we wouldn't want to add anything that isn't a String. The compiler error happens because Abc<Def<Ghi<String>>> is not assignable to ?.

listTwo.add(abcdef) (line 2) is valid because List<Abc<?>> represents a list of Abcs of any type. That's right - nested wildcards are different from top-level wildcards in that they represent any type rather than some specific type (in other words, nested wildcards don't capture). The compiler allows it because Abc<Def<Ghi<String>>> is assignable to Abc<?>. See this post for further discussion of nested wildcards: Multiple wildcards on a generic methods makes Java compiler (and me!) very confused

listThree.add(abcdef) (line 3) is invalid because List<Abc<Def<?>>> represents a list of Abcs of Defs of any type. Generics are not covariant, so Abc<Def<Ghi<String>>> is not assignable to Abc<Def<?>>, even though Def<Ghi<String>> is assignable to Def<?>. A List<Integer> isn't assignable to a List<Number> for the same reason. See this post for further explanation: Is List<Dog> a subclass of List<Animal>? Why aren't Java's generics implicitly polymorphic?

listFour.add(abcdef) (line 4) is invalid for the same reason - Abc<Def<Ghi<String>>> is not assignable to Abc<Def<Ghi<?>>>.

listFive.add(abcdef) (line 5) is valid because the generic types match exactly - Abc<Def<Ghi<String>>> is obviously assignable to Abc<Def<Ghi<String>>>.

Bombay answered 19/6, 2014 at 16:10 Comment(2)
Wanted to follow-up to note that the assignability problems of lines 3 and 4 can be resolved by changing the declarations from List<Abc<Def<?>>> listThree to List<Abc<? extends Def<?>>> listThree and from List<Abc<Def<Ghi<?>>>> to List<Abc<? extends Def<? extends Ghi<?>>>> listFour. Thanks for the concise explanation and useful links; it's much clearer now.Krishna
@PaulBellora That's right - nested wildcards are different from top-level wildcards in that they represent any type rather than some specific type this! very much this!Proconsul

© 2022 - 2024 — McMap. All rights reserved.