Stream.findFirst different than Optional.of?

后端 未结 5 1211
旧时难觅i
旧时难觅i 2021-02-18 19:13

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

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

    public Optional getItems(List&         


        
相关标签:
5条回答
  • 2021-02-18 20:01

    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.

    0 讨论(0)
  • 2021-02-18 20:04

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

    0 讨论(0)
  • 2021-02-18 20:08

    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

    0 讨论(0)
  • 2021-02-18 20:14

    If the class B inherits class A, that doesn't mean Optional inherits Optional. Optional is a different class.

    0 讨论(0)
  • 2021-02-18 20:15

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

    0 讨论(0)
提交回复
热议问题