Stream.findFirst和Optional.of有什么不同?

21

假设我有两个类和两个方法:

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>

所以,当我使用findFirst返回的Optional值,并再次使用Optional.of进行包装时,编译器会认识到继承关系,但如果我直接使用findFirst的结果,则无法识别。


4
如果我理解正确的话,更容易重现 Optional<A> a = Optional.of(new B()); Optional<A> b = Stream.of(new B()).findFirst(); 。主要原因是 Optional.of 的类型通过上下文推断,因为其签名暗示着 <T> Optional<T> of(T value) - Naman
1
@nullpointer 是的,你已经创建了最小的示例,但我认为问题中的示例更接近于原始代码。 - Holger
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调用提供明确类型的解决方案。


嗨,Holger。这甚至比我最初接受的答案更好(https://dev59.com/9VQJ5IYBdhLWcg3wAA7z#54801033)。 - keanni

8
你遇到的问题涉及泛型继承。Optional< B > 无法作为 Optional< A > 返回,因为它没有继承 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();
}

根据您的需求,这可能是可以正常工作的。

编辑:转义一些字符


我会接受这个答案,因为它还提供了第一种方法如何工作的信息(尽管我没有明确要求,但我对解决方案感到满意)。 - keanni
3
这里有一个泛型使用的指南,它说:“应避免将通配符用作返回类型,因为它会强制程序员在使用代码时处理通配符”,从我的实际经验来看,这是一条好建议。毕竟,返回Optional<A>很容易做到,可以参考此答案 - Holger

6
Optional<B> 不是 Optional<A> 的子类。
在第一个情况下,你有一个 Stream<B>,因此 findFirst 返回一个 Optional<B>,它不能转换为一个 Optional<A>
在第二种情况下,您有一个流水线,返回一个 B 实例。当您将该实例传递给 Optional.of() 时,编译器看到方法的返回类型是 Optional<A>,所以 Optional.of() 返回一个 Optional<A>(因为 B 扩展了 A,所以 Optional<A> 可以将 B 实例作为其值)。

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(); 返回的类型并没有基于 getItems 声明的返回类型进行标记(T 严格基于 Stream<T> 的类型参数)


2
如果B类继承A类,这并不意味着Optional继承了Optional。Optional是一个不同的类。"最初的回答"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接