Java 8中的findFirst与Optional中的map区别

7

考虑以下代码:

class Foo {
  Integer attr;
  public Integer getAttr() {return attr;}
}

List<Foo> list = new ArrayList<>();
list.add(new Foo());

list.stream().map(Foo::getAttr).findAny().orElse(null);  //A
list.stream().findAny().map(Foo::getAttr).orElse(null);  //B

Line A 抛出异常

java.lang.NullPointerException: null

而 Line B 返回 null。

这种行为的原因是什么?findAny()map() 都返回 Optional<T>


4个回答

7
list.stream().map(Foo::getAttr).findAny().orElse(null);

Java流文档说,Stream:"返回一个由将给定函数应用于此流的元素的结果组成的流",而findAny() "可能返回NullPointerException - 如果所选元素为空"。在您的类Foo中,默认情况下将Integer(而不是int)设置为null,因为它被声明但未初始化。请参见原始类型查看默认值Java中的对象初始化

初始化对于: A)类成员(对象和基本类型) B)局部变量是不同的


你应该编辑你的回答并添加来源 ;) 我知道它们,但这样更好。 - AxelH

5
首先,你的两个代码片段 map 是不同的操作:
//            v--- stream intermediate operation
list.stream().map(Foo::getAttr).findAny().orElse(null);  //A
//                      v---- a Optional utility method 
list.stream().findAny().map(Foo::getAttr).orElse(null);  //B

当在Stream#findAny操作中出现NullPointerException时,是因为它不能接受null值。由于它使用的是Optional.of而不是Optional.ofNullable。而Stream#findAny文档已经断言:

Throws:

NullPointerException - 如果所选元素为null

所以,如果你想让你的代码片段A正常工作,你必须在调用Stream#findAny之前过滤掉所有的null值,例如:

//when no elements in stream, `findAny` will be return a empty by Optional.empty()
//                                                       v   
list.stream().map(Foo::getAttr).filter(Objects::nonNull).findAny().orElse(null);//A

1
干得好!我以为没有人会谈论 filter(Objects::nonNull) - fps

4
显然,这是因为您执行这些操作的顺序以及findAny明确表示:如果所选元素为空,则抛出NullPointerException

当您执行map(Foo::getAttr)时,您已经将其映射为null,因此您的流现在包含一个null;因此findAny会因为应用于该null而导致异常。

另一种操作先找到Foo对象,然后将其映射到Foo::getAttr(因此将其映射到Optional.empty()),因此调用了orElse

此外,以下内容更容易理解(至少对我来说):

 list.stream()
     .findAny()
     .flatMap(f -> Optional.ofNullable(f.getAttr()))
     .orElse(null);

flatMap 将映射到 Optional<Integer>(属性),如果这个值是 empty,则返回 orElse 的结果。


3
嗨,将Optional映射为null是不可能的。一个Optional要么为空,要么映射到非null值。原帖中的第二种情况,对可选项调用map(Foo :: getAttr),与您的flatMap(f - > Optional.ofNullable(f.getAttr()))完全相同。在任一情况下,结果都是一个空的可选项,并返回传递给orElse的参数。(而说“orElse没有被调用”毫无意义。无论如何,都不能跳过调用orElse)。 - Holger
@Holger 呜,那是我做得很蠢,谢谢。 - Eugene

0
list.stream().map(Foo::getAttr)

...返回一个具有一个元素的流,该元素的值为null。

findAny()(和findFirst())的JavaDoc说:

返回:

描述此流的某些元素的Optional,如果流为空,则返回空的Optional

抛出:

NullPointerException-如果所选元素为null

因此,findAny()正如文档所述:它选择了一个null,结果抛出NullPointerException

这是有道理的,因为Optional是一个容器对象,根据JavaDoc(但强调是我的):

可能包含非null值的容器对象

这意味着您可以保证Optional.ifPresent(x -> x.method())永远不会由于x为null而抛出NullPointerException

findAny() 无法返回 Optional.of(null)。而 Optional.empty() 表示流为空,而不是找到了 null。

Stream/Optional 基础设施的许多部分都是为了防止使用 null。

您可以通过将 null 映射到 Optionals 来解决这个问题,以产生一个 Optional<Optional<Foo>> -- 这看起来有点复杂,但是它是您领域的准确表示。 Optional.empty() 表示流为空。 Optional.of(Optional.empty()) 表示找到了一个 null 元素:

list.stream().map(Foo::getAttr).map(Optional::ofNullable).findAny()

3
使用 flatMap 会让它更加简洁:list.stream().findAny().flatMap(f -> Optional.ofNullable(f.getAttr())).orElse(null); 就像我上面写的一样... - Eugene
1
如果你想把null值排除在外,是的。但在我看来,这段代码可以成为一个边界,将可能存在null值的领域与一切都保证非空的领域分开。 - slim

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