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

Why does findFirst() throw a NullPointerException if the first element it finds is null?

为什么会抛出 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() returns 和 Optional,这使得 findFirst() 能够处理 null 更有意义。

编辑:更新了 orElse() 以减少歧义。

原因是在 return 中使用了 Optional<T>。 Optional 不允许包含 null。从本质上讲,它无法区分“不存在”和“存在,但设置为 null”的情况。

所以the documentation明确禁止在findFirst()中选择null的情况:

Throws:

NullPointerException - if the element selected is null

Optional 应该是 "value" 类型。 (阅读 javadoc 中的细则:)JVM 甚至可以仅用 Foo 替换所有 Optional<Foo>,消除所有装箱和拆箱成本。 null Foo 表示空 Optional<Foo>.

允许 Optional 具有 null 值而不添加布尔标志是一种可能的设计 - 只需添加一个哨兵对象。 (甚至可以使用 this 作为哨兵;参见 Throwable.cause)

Optional 不能包装 null 的决定不是基于运行时成本。这是一个争论不休的问题,您需要挖掘邮件列表。这个决定对每个人来说都没有说服力。

无论如何,由于 Optional 不能包装 null 值,所以在 findFirst 这样的情况下它把我们逼到了墙角。他们一定认为空值非常罕见(甚至认为 Stream 应该禁止空值),因此在空值上抛出异常比在空流上抛出异常更方便。

解决方法是框 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())

到信息流。如果没有第一个元素或者第一个元素是 null,那么在这两种情况下,结果都将是一个空的可选值。所以在你的情况下,你可以使用

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

这与您更新后的问题没有太大区别。您只需将 "no such element" 替换为 "StringWhenListIsEmpty",将 "first element is null" 替换为 null。但是如果你不喜欢条件语句,你也可以像这样实现它:

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

现在,如果元素存在,firstString 将是 null,但是当元素不存在时,null 将是 "StringWhenListIsEmpty"

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

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

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

这个解决方案的美妙之处在于 Optional 没有分配并且 BinaryOperator lambda 不会分配任何东西。

您可以在查找

之前使用java.util.Objects.nonNull过滤列表

类似于

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