Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stream.findFirst different than Optional.of?

Let's say I have two classes and two methods:

class Scratch {
    private class A{}
    private class B extends A{}

    public Optional<A> getItems(List<String> items){
        return items.stream()
             .map(s -> new B())
             .findFirst();
    }

    public Optional<A> getItems2(List<String> items){
        return Optional.of(
            items.stream()
                 .map(s -> new B())
                 .findFirst()
                 .get()
        );
    }
}

Why does getItems2 compile while getItems gives compiler error

incompatible types: java.util.Optional<Scratch.B> cannot be converted to java.util.Optional<Scratch.A>

So when I get the value of the Optional returned by findFirst and wrap it again with Optional.of the compiler recognizes the inheritance but not if I use directly the result of findFirst.

like image 437
keanni Avatar asked Feb 21 '19 06:02

keanni


3 Answers

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.

like image 52
Holger Avatar answered Nov 04 '22 02:11

Holger


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

like image 41
Worthless Avatar answered Nov 04 '22 01:11

Worthless


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)).

like image 21
Eran Avatar answered Nov 04 '22 01:11

Eran