Reputation: 103
I've come across an oddity I can't explain.
The following code (a minimal example) won't compile:
class Test {
interface I {}
Optional<I> findOne(ArrayList<? extends I> list) {
return list.stream()
.findFirst();
}
}
javac (version 11 at least) says:
error: incompatible types: Optional<CAP#1> cannot be converted to Optional<I>
.findFirst();
^
where CAP#1 is a fresh type-variable:
CAP#1 extends I from capture of ? extends I
However, I sort of randomly found that adding a seemingly redundant .map() call to the stream [edit: it's actually added to the Optional returned by findFirst()] compiles fine:
class Test {
interface I {}
Optional<I> findOne(ArrayList<? extends I> list) {
return list.stream()
.findFirst()
.map(a -> a);
}
}
I'm curious about what is happening there that adding the .map() call lets it compile.
[I know that changing the method parameter to take an ArrayList<I>
works, but that's not what I'm asking about.]
Upvotes: 3
Views: 89
Reputation: 25903
The first does not compile because Java generics are invariant.
An Optional<Dog>
is completely different to an Optional<Animal>
, hence Optional<...>
with ...
being the type your ?
captured is not compatible to Optional<I>
.
To elaborate, consider the following example:
List<Dog> dogs = new ArrayList<>();
List<Animal> animals = dogs; // this does not compile, but pretend it would
animals.add(new Cat());
Dog dog = dogs.get(0); // should be safe, but its actually a cat!
But with the map(a -> a)
you have an implicit upcast of the type, like (Animal) dog
which makes this actually an Optional<Animal>
. So Optional<I>
instead of Optional<...>
in your case.
So your a -> a
lambda, which looks innocent actually is a method taking Dog
(or ...
from ?
in your case) and giving out an Animal
(or I
):
a -> (I) a
Consider the following example
Optional<Dog> dog = Optional.of(new Dog());
Optional<Animal> animal = dog.map(d -> d);
which demonstrates exactly your situation. Because of invariance you can not just assign it, you have to actively convert it. And because of context, Java can implicitly cast the d
to Animal
. So the code is equivalent to
dog.map(d -> (Animal) d);
Note that the map
method you call here is not the map
from Stream
but from Optional
, which is the result of findFirst
. So its purpuse is to map from an Optional<X>
to an Optional<Y>
by applying the given transformation.
Upvotes: 4