0

I have read some article about generic covariant, and a learn a common knowledge about covariant and contravariant describe how the subtype relation is affected by type transformations. That is, if A and B are types, f is a type transformation, and ≤ the subtype relation (i.e. A ≤ B means that A is a subtype of B), we have

  • f is covariant if A ≤ B implies that f(A) ≤ f(B)
  • f is contravariant if A ≤ B implies that f(B) ≤ f(A)
  • f is invariant if neither of the above holds

as we know, ? extends T is covariant, so we can say:
because of ? extends Number ≤ Number
so List<? extends Number> ≤ List<Number>, and this means List<? extends Number> is a subtype of List<Number>

List<? extends Number> a1 = null;
List<Number> a2 = null;
a2 = a1; // compile error
a1 = a2; // compile success

so why a1 = a2 can be compiled, but a2 = a1 can't.
Don't List<? extends Number> is a subtype of List<Number> means a2 = a1 ?
wait and hope your answer !

wanglong
  • 89
  • 5
  • 2
    "... this means `List extends Number>` is a subtype of `List`" — No, it doesn't. `List` is a subtype of `List extends Number>`. – khelwood Mar 26 '21 at 08:49
  • No, `List extends Number>` is not a subtype of `List` (don't of subtypes and the like when dealing with generics) but it can be assigned any list whose generic type is `Number` or a subtype of that. So `a2 = a1` fails because `a2` explicitly expects a `List` while `a1` could actually be a `List` etc. which doesn't fit. – Thomas Mar 26 '21 at 08:50
  • does covariant means type relationship transform ? @khelwood – wanglong Mar 26 '21 at 08:50
  • I don't know what you mean by "type relationship transform" – khelwood Mar 26 '21 at 08:52
  • Ah, did you read "type transformation" from [this post](https://stackoverflow.com/a/8482091/5133585)? Your reasoning is not sound. What do you define `f` as? You seem to have defined it as `f(A) = List`, but this is an invariant transformation, so you can't apply that rule about covariant transformations. – Sweeper Mar 26 '21 at 08:56
  • If instead you define `f(A) = List extends A>`, which is a covariant transformation. Then you can apply the rule to, say, `Integer` and `Number`. `Integer ≤ Number`, so `List extends Integer> ≤ List extends Number>` holds. – Sweeper Mar 26 '21 at 08:59
  • so Is there any specific result for `List extends Number> arr = new ArrayList()`, why List is subtype of List extends Number>, is it because of Number is only an element of `? extends Number`? how to explain... @Thomas @Sweeper @khelwood – wanglong Mar 26 '21 at 09:31

1 Answers1

2

I'll try to explain a little more: <? extends Number> is a bounded wildcard so this is not a type but more like a placeholder. It states that the actual generic type can be Number or a subtype.

So the following would be valid:

List<? extends Number> numberList = new ArrayList<Number>();
List<? extends Number> integerList = new ArrayList<Integer>();
List<? extends Number> doubleList = new ArrayList<Double>();

You can also assign them:

integerList = numberList; 
numberList = doubleList;
doubleList = integerList;

Why does this work? Because the compiler imposes some restrictions, e.g. you are not allowed to add elements to those lists (actually no method taking a wildcard parameter would be allowed).

Why can't you add to those? Because after the above reassignments a doubleList.add(Double.valueOf(1.5) would be fatal because the variable would actually point to a list that was defined to only contain integers (new ArrayList<Integer>()).

List<? extends Number> actually just tells the compiler that the list allows elements that extend Number you could retrieve those elements and assign them to Number:

for( Number element : numberList ) { ... }
for( Number element : integerList ) { ... }
for( Number element : doubleList) { ... }

Why is List<Number> different? Because it only defines that all elements are instances of Number and thus adding and retrieving numbers are allowed.

The assignment List<Number> numberList = new ArrayList<Integer>() is not allowed because you could still call numberList.add(Double.valueOf(1.23)) which would be logically invalid (see above).

Since List<? extends Number> integerList = new ArrayList<Integer>(); is valid, List<Number> numberList = integerList; is not - this would transitively allow invalid assignments which the compiler should prevent (that's why you use generics).

Thomas
  • 87,414
  • 12
  • 119
  • 157
  • https://pingfangx.github.io/java-tutorials/java/generics/subtyping.html this article say List is a subtype of List extends Number>, but not have any explain, maybe it is an appointment. – wanglong Mar 26 '21 at 10:13
  • @wanglong well, you could think of that relation in terms of types and subtypes but you need to be aware that those behave differently than normal types. Even though `Integer` is a subtype of `Number`, a `List` is not a "subtype" of `List` and even though you could say that in some sense a `List` is a "subtype" of `List extends Number>` you can't use both the same way. – Thomas Mar 26 '21 at 10:23