Let\'s say I have two classes and two methods:
class Scratch {
private class A{}
private class B extends A{}
public Optional getItems(List&
An Optional<B>
is not a subtype of Optional<A>
. Unlike other programming languages, Java’s generic type system does not know “read only types” or “output type parameters”, so it doesn’t understand that Optional<B>
only provides an instance of B
and could work at places where an Optional<A>
is required.
When we write a statement like
Optional<A> o = Optional.of(new B());
Java’s type inference uses the target type to determine that we want
Optional<A> o = Optional.<A>of(new B());
which is valid as new B()
can be used where an instance of A
is required.
The same applies to
return Optional.of(
items.stream()
.map(s -> new B())
.findFirst()
.get()
);
where the method’s declared return type is used to infer the type arguments to the Optional.of
invocation and passing the result of get()
, an instance of B
, where A
is required, is valid.
Unfortunately, this target type inference doesn’t work through chained invocations, so for
return items.stream()
.map(s -> new B())
.findFirst();
it is not used for the map
call. So for the map
call, the type inference uses the type of new B()
and its result type will be Stream<B>
. The second problem is that findFirst()
is not generic, calling it on a Stream<T>
invariably produces a Optional<T>
(and Java’s generics does not allow to declare a type variable like <R super T>
, so it is not even possible to produce an Optional<R>
with the desired type here).
→ The solution is to provide an explicit type for the map
call:
public Optional<A> getItems(List<String> items){
return items.stream()
.<A>map(s -> new B())
.findFirst();
}
Just for completeness, as said, findFirst()
is not generic and hence, can’t use the target type. Chaining a generic method allowing a type change would also fix the problem:
public Optional<A> getItems(List<String> items){
return items.stream()
.map(s -> new B())
.findFirst()
.map(Function.identity());
}
But I recommend using the solution of providing an explicit type for the map
invocation.
Look at this similar example:
Optional<A> optA = Optional.of(new B()); //OK
Optional<B> optB = Optional.of(new B()); //OK
Optional<A> optA2 = optB; //doesn't compile
You can make the second method fail by rewriting it as:
public Optional<A> getItems2(List<String> items) {
return Optional.<B>of(items.stream().map(s -> new B()).findFirst().get());
}
This is simply because generic types are invariant.
Why the difference? See the declaration of Optional.of
:
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
The type of the optional is picked up from the target of the assignment (or return type in this case).
And Stream.findFirst()
:
//T comes from Stream<T>, it's not a generic method parameter
Optional<T> findFirst();
In this case, however, return items.stream().map(s -> new B()).findFirst();
doesn't type the result of .findFirst()
based on the declared return type of getItems
(T
is strictly based on the type argument of Stream<T>
)
The issue you have is with inheritance for generics. Optional< B > doesn't extend Optional< A >, so it can't be returned as such.
I'd imagine that something like this:
public Optional<? extends A> getItems( List<String> items){
return items.stream()
.map(s -> new B())
.findFirst();
}
Or:
public Optional<?> getItems( List<String> items){
return items.stream()
.map(s -> new B())
.findFirst();
}
Would work fine, depending on your needs.
Edit: escaping some characters
If the class B inherits class A, that doesn't mean Optional inherits Optional. Optional is a different class.
An Optional<B>
is not a sub-class of Optional<A>
.
In the first case, you have a Stream<B>
, so findFirst
returns an Optional<B>
, which cannot be converted to an Optional<A>
.
In the second case, you have a stream pipeline that returns an instance of B
. When you pass that instance to Optional.of()
, the compiler sees that the return type of the method is Optional<A>
, so Optional.of()
returns an Optional<A>
(since an Optional<A>
can hold an instance of B
as its value (since B
extends A
)).