Stream.findFirst与Optional.of不同?

问题描述 投票:21回答:5

可以说我有两个类和两个方法:

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()
        );
    }
}

为什么getItems2编译而getItems给编译器错误

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

因此,当我get Optional返回的findFirst的值并再次用Optional.of包装时,编译器会识别继承但不是直接使用findFirst的结果。

java java-stream optional
5个回答
21
投票

Optional<B>不是Optional<A>的子类型。与其他编程语言不同,Java的泛型类型系统不知道“只读类型”或“输出类型参数”,因此它不理解Optional<B>只提供B的实例,并且可以在需要Optional<A>的地方工作。

当我们写一个类似的声明时

Optional<A> o = Optional.of(new B());

Java的类型推断使用目标类型来确定我们想要的

Optional<A> o = Optional.<A>of(new B());

有效的new B()可用于需要A实例的地方。

这同样适用于

return Optional.of(
        items.stream()
             .map(s -> new B())
             .findFirst()
             .get()
    );

其中方法声明的返回类型用于推断Optional.of调用的类型参数并传递get()的结果,B的实例(需要A)是有效的。

不幸的是,这种目标类型推断不能通过链式调用工作,所以对于

return items.stream()
     .map(s -> new B())
     .findFirst();

它不用于map电话。所以对于map调用,类型推断使用new B()的类型,其结果类型将是Stream<B>。第二个问题是findFirst()不是通用的,在Stream<T>上调用它总是产生一个Optional<T>(并且Java的泛型不允许声明像<R super T>这样的类型变量,因此甚至不可能在这里生成具有所需类型的Optional<R>) 。

→解决方案是为map调用提供显式类型:

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

只是为了完整性,如上所述,findFirst()不是通用的,因此,不能使用目标类型。链接允许类型更改的通用方法也可以解决问题:

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

但我建议使用为map调用提供显式类型的解决方案。


8
投票

你遇到的问题是泛型的继承。可选<B>不扩展Optional <A>,因此无法返回。

我想象这样的事情:

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

要么:

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

根据您的需要,可以正常工作。

编辑:转义一些字符


6
投票

Optional<B>不是Optional<A>的子类。

在第一种情况下,你有一个Stream<B>,所以findFirst返回一个Optional<B>,不能转换为Optional<A>

在第二种情况下,您有一个流管道,它返回B的实例。当你将该实例传递给Optional.of()时,编译器会看到方法的返回类型是Optional<A>,因此Optional.of()返回Optional<A>(因为Optional<A>可以保存B的实例作为其值(因为B扩展了A))。


3
投票

看看这个类似的例子:

Optional<A> optA = Optional.of(new B()); //OK
Optional<B> optB = Optional.of(new B()); //OK
Optional<A> optA2 = optB;                //doesn't compile

您可以通过将其重写为第二种方法失败:

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

这只是因为泛型类型是不变的。


为什么不同?请参阅Optional.of的声明:

public static <T> Optional<T> of(T value) {
    return new Optional<>(value);
}

从赋值的目标(或在这种情况下返回类型)中选择可选的类型。

Stream.findFirst()

//T comes from Stream<T>, it's not a generic method parameter
Optional<T> findFirst(); 

但是,在这种情况下,return items.stream().map(s -> new B()).findFirst();不会根据.findFirst()的声明返回类型键入getItems的结果(T严格基于Stream<T>的类型参数)


2
投票

如果B类继承A类,那并不意味着Optional继承Optional。可选是一个不同的类。

© www.soinside.com 2019 - 2024. All rights reserved.