如果它找到的第一个元素为 null,那么 findFirst()为什么会抛出 NullPointerException?

为什么会出现 java.lang.NullPointerException呢?

List<String> strings = new ArrayList<>();
strings.add(null);
strings.add("test");


String firstString = strings.stream()
.findFirst()      // Exception thrown here
.orElse("StringWhenListIsEmpty");
//.orElse(null);  // Changing the `orElse()` to avoid ambiguity

strings中的第一项是 null,这是一个完全可以接受的值。此外,findFirst()返回一个 可以选择,这使得 findFirst()能够处理 null更有意义。

EDIT: updated the orElse() to be less ambiguous.

124588 次浏览

The reason for this is the use of Optional<T> in the return. Optional is not allowed to contain null. Essentially, it offers no way of distinguishing situations "it's not there" and "it's there, but it is set to null".

这就是为什么 the documentation明确禁止在 findFirst()中选择 null的情况:

投掷:

如果选择的元素是 null

可选类型应该是“值”类型。JVM 甚至可以仅用 Foo代替所有的 Optional<Foo>,从而消除所有装箱和拆箱成本。一个 null Foo 表示一个空的 Optional<Foo>

这是一个可能的设计,允许可选的空值,没有添加布尔标志-只需添加一个哨兵对象。(甚至可以使用 this作为哨兵; 参见 Throwable.cause)

可选项不能包装 null 的决策不是基于运行时成本的。这是一个非常有争议的问题,你需要挖掘邮件列表。这个决定并不能说服所有人。

在任何情况下,由于 Options 不能包装 null 值,所以在像 findFirst这样的情况下,它会把我们推到一个角落。他们肯定有理由认为 null 值非常罕见(甚至认为 Stream 应该禁止 null 值) ,因此在 null 值上抛出异常比在空流上抛出异常更方便。

变通方法是将 null打包,例如。

class Box<T>
static Box<T> of(T value){ .. }


Optional<Box<String>> first = stream.map(Box::of).findFirst();

(他们说每个 OOP 问题的解决方案都是引入另一种类型:)

作为 已经讨论过了,API 设计人员不假定开发人员希望以同样的方式处理 null值和缺失值。

如果您仍然希望这样做,可以通过应用序列显式地进行此操作

.map(Optional::ofNullable).findFirst().flatMap(Function.identity())

to the stream. The result will be an empty optional in both cases, if there is no first element or if the first element is null. So in your case, you may use

String firstString = strings.stream()
.map(Optional::ofNullable).findFirst().flatMap(Function.identity())
.orElse(null);

如果第一个元素不存在或者 null不存在,则获取 null值。

如果你想区分这些情况,你可以简单地省略 flatMap步骤:

Optional<String> firstString = strings.stream()
.map(Optional::ofNullable).findFirst().orElse(null);
System.out.println(firstString==null? "no such element":
firstString.orElse("first element is null"));

This is not much different to your updated question. You just have to replace "no such element" with "StringWhenListIsEmpty" and "first element is null" with null. But if you don’t like conditionals, you can achieve it also like:

String firstString = strings.stream().skip(0)
.map(Optional::ofNullable).findFirst()
.orElseGet(()->Optional.of("StringWhenListIsEmpty"))
.orElse(null);

现在,如果一个元素存在但是是 null,那么 firstString将是 null,如果没有元素存在,那么它将是 "StringWhenListIsEmpty"

以下代码将 findFirst()替换为 limit(1),并将 orElse()替换为 reduce():

String firstString = strings.
stream().
limit(1).
reduce("StringWhenListIsEmpty", (first, second) -> second);

limit()只允许1个元素到达 reduce。传递给 reduceBinaryOperator返回1个元素,如果没有元素到达 reduce,则返回 "StringWhenListIsEmpty"

这个解决方案的美妙之处在于,Optional不被分配,而 BinaryOperator lambda 不会分配任何东西。

可以使用 java.util.Objects.nonNull在查找之前筛选列表

比如

list.stream().filter(Objects::nonNull).findFirst();