1

I was reading an interesting dzone article on covariance in java which is pretty easy to follow but there is one thing bugging me which doesnt make sense, the article is here https://dzone.com/articles/covariance-and-contravariance

I am quoting examples from the article here where it is explaining why a collection cannot be added to:

With covariance we can read items from a structure, but we cannot write anything into it. All these are valid covariant declarations.

List<? extends Number> myNums = new ArrayList<Integer>();

Because we can be sure that whatever the actual list contains, it can be upcasted to a Number (after all anything that extends Number is a Number, right?) However, we are not allowed to put anything into a covariant structure.

myNumst.add(45); //compiler error

This would not be allowed because the compiler cannot determine what is the actual type of the object in the generic structure. It can be anything that extends Number (like Integer, Double, Long), but the compiler cannot be sure what

The paragraph above is what doesn't make sense to me, the compiler knows that the list contains Number or anything that extends it, and the ArrayList is typed to Integer. And the compiler knows about the literal int that is inserted.

So why does it enforce this as it seems to me like it can determine the type?

berimbolo
  • 3,319
  • 8
  • 43
  • 78
  • The compiler knows that the permitted contents of `myNums` are some _particular_ subtype of `Number`, but it doesn't know which one. Is an integer an instance of `>` ? It doesn't know, because it doesn't know exactly what type `>` is. – khelwood Jun 29 '17 at 08:03
  • Ok so the compiler doesnt look at the right hand of the assigment statement and it doesnt consider literals? – berimbolo Jun 29 '17 at 08:05
  • The variable has a declared type, which is `List extends Number>`. The compiler considers the declared type of the variable when deciding what operations are possible, it doesn't try and judge what run-time type it is. But it's up to you to give the variable the type you want. – khelwood Jun 29 '17 at 08:08
  • ok what you are saying does make sense, although what I am thinking is sure it doesnt know what `?` is but it does know that `Integer` extends `Number`? – berimbolo Jun 29 '17 at 08:08
  • Ok yes your second comment helps me understand so ignore my second comment! – berimbolo Jun 29 '17 at 08:09
  • This is the difference between `List extends Number>` and `List`. In the first case, we have some unknown that just needs to match `Number`. In the seconds case we can have any unknown that extends `Number`. We can't tell from `? extends Number` that `?` is an integer, but with just `Number` we can. – flakes Jun 29 '17 at 08:11
  • Ok, my confusion is caused by the ArrayList typing which is `Integer` coupled with the fact that the attempt is to insert a literal int. – berimbolo Jun 29 '17 at 08:13
  • Possible duplicate of [Can't add value to the Java collection with wildcard generic type](https://stackoverflow.com/questions/3716920/cant-add-value-to-the-java-collection-with-wildcard-generic-type) – Tom Jun 29 '17 at 08:19
  • "but we cannot write anything into it" except `null`. – Andy Turner Jun 29 '17 at 08:23
  • Anyway: nicely written question; giving interesting food for thought. – GhostCat Jun 29 '17 at 09:06

3 Answers3

5

When the compiler comes across:

List<? extendsNumber> myNums = new ArrayList<Integer>();

it checks that the types on left and right hand side fit together. But the compiler does not "remember" the specific type used for the instantiation on the right hand side.

One could say that the compiler remembers this about myNums:

  • it has been initialized
  • it is of type List<? extendsNumber>

Yes compilers can do constant folding; and data flow analysis; and it might be possible for a compiler to track that "instantiation type" information as well - but alas: the java compiler doesn't do that.

It only knows that myNums is an initialized List<? extends Numbers> - nothing more.

GhostCat
  • 137,827
  • 25
  • 176
  • 248
4

You are missing two points:

  • You are thinking in terms of local variables only:

    public void myMethod() {
       List<? extends Number> list = new ArrayList<Integer>();
       list.add(25);
    }
    

    A compiler could easily detect the actual value of ?, here but I know of nobody who would write such a code; if you are going to use an integer list you just declare the variable as List<Integer>.

    Covariance and contravariance are most useful when dealing with parameters and/or results; this example is more realistic:

    public List<Integer> convert(List<? extends Number> source) {
       List<Integer> target = new ArrayList<>();
       for (Number number : source) {
          target.add(number.intValue());
       }
       return target;
    }
    

    How is a compiler expected to know which is the type used to parametrize the list? Even if at compile time all the calls only pass instances of ArrayList<Integer>, at a later time code can use the method with a diferent parameter without the class being recompiled.

  • The paragraph above is what doesn't make sense to me, the compiler knows that the list contains Number or anything that extends it, and the ArrayList is typed to Integer. And the compiler knows about the literal int that is inserted.

    No, what the compiler knows is that the list contains something that extends Number (including Number). It cannot tell if it is a List<Number> (in which case you may insert any instance of Number) or a List<Integer> (in which case you may only insert Integer instances). But it does know that everything you retrieve using get will be an instance of Number (even if it is not sure about the specific class).

Hoopje
  • 12,677
  • 8
  • 34
  • 50
SJuan76
  • 24,532
  • 6
  • 47
  • 87
  • Great answer, thanks. Much easier to understand with this example. – berimbolo Jun 29 '17 at 08:28
  • Worth mentioning that `? extends class` is often useful when using accepting a class with a type parameter. If `class Foo extends Bar` then a method `void fizz(List extends Bar> bar)` can accept a `List` without casting. On the otherhand `void fizz(List bar)` would fail when passed a `List` – flakes Jun 29 '17 at 08:40
  • 2
    @flakes this is what is described in EJ 2nd Ed Item 28: "Use bounded wildcards to increase API flexibility". The other important thing it says in there is not to use wildcards in return types: " If the user of a class has to think about wildcard types, there is probably something wrong with the class’s API". To put it another way, you shouldn't ever really need wildcards in local variable declarations, aside from parameters. – Andy Turner Jun 29 '17 at 08:50
  • Agreed plus1; great answer. I thought about mentioning that "local" versus "class" scope too, but basically forgot about it. Nice to see that this "gap" is filled by your question ;-) – GhostCat Jun 29 '17 at 08:52
4

The paragraph above is what doesn't make sense to me, the compiler knows that the list contains Number or anything that extends it, and the ArrayList is typed to Integer. And the compiler knows about the literal int that is inserted.

Consider a slightly different but related example:

Object obj = "";

By the argument above, the compiler should also be able to know that obj is actually a String, and so you'd be able to invoke String-specific methods on it:

obj.substring(0);

which anybody with even a little Java experience knows you can't.

You (and only you) (or, at least, the person who wrote the code) has the type information, and a wilful decision has been made to discard it: there is no reason to have declared the variable type to be Object, so why should the compiler have to put in work to try to recover that information? (*)

By the same token, if you want the compiler to know that the value of the variable

List<? extends Number> myNums = new ArrayList<Integer>();

is an ArrayList<Integer>, declare the variable to be of that type. Otherwise, it's just much simpler for the compiler to assume "this can be absolutely anything within the type bounds".


(*) I read this argument somewhere in another answer somewhere on SO. I can't remember where it was, or I'd give appropriate attribution.

Andy Turner
  • 137,514
  • 11
  • 162
  • 243