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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/32466799/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 12:36:24  来源:igfitidea点击:

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

javajava-8java-streamoptional

提问by neverendingqs

Why does this throw a java.lang.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

The first item in stringsis null, which is a perfectly acceptable value. Furthermore, findFirst()returns an Optional, which makes even more sense for findFirst()to be able to handle nulls.

中的第一项stringsnull,这是一个完全可以接受的值。此外,findFirst()返回一个Optional,这对于findFirst()能够处理nulls更有意义。

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

编辑:更新orElse()为不那么模棱两可。

采纳答案by dasblinkenlight

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".

这样做的原因是Optional<T>在返回中使用了。Optional 不允许包含null. 从本质上讲,它无法区分“它不在那里”和“它在那里,但它被设置为null”的情况。

That's why the documentationexplicitly prohibits the situation when nullis selected in findFirst():

这就是为什么文档明确禁止在以下情况下null选择的情况findFirst()

Throws:

NullPointerException- if the element selected is null

抛出:

NullPointerException- 如果选择的元素是 null

回答by ZhongYu

Optional is supposed to be a "value" type. (read the fine print in javadoc:) JVM could even replace all Optional<Foo>with just Foo, removing all boxing and unboxing costs. A nullFoo means an empty Optional<Foo>.

Optional 应该是“值”类型。(阅读javadoc中的细则:) JVM 甚至可以将 all 替换Optional<Foo>为 just Foo,从而消除所有装箱和拆箱成本。一个nullFoo 意味着一个空的Optional<Foo>

It is a possible design to allow Optional with null value, without adding a boolean flag - just add a sentinel object. (could even use thisas sentinel; see Throwable.cause)

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

The decision that Optional cannot wrap null is not based on runtime cost. This was a hugely contended issue and you need to dig the mailing lists. The decision is not convincing to everybody.

Optional 不能包装 null 的决定不是基于运行时成本。这是一个极具争议的问题,您需要挖掘邮件列表。这个决定并不能让所有人信服。

In any case, since Optional cannot wrap null value, it pushes us in a corner in cases like findFirst. They must have reasoned that null values are very rare (it was even considered that Stream should bar null values), therefore it is more convenient to throw exception on null values instead of on empty streams.

在任何情况下,由于 Optional 不能包装空值,它在像findFirst. 他们一定有理由认为空值非常罕见(甚至认为 Stream 应该禁止空值),因此对空值而不是空流抛出异常更方便。

A workaround is to box null, e.g.

一种解决方法是装箱null,例如

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

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

(They say the solution to every OOP problem is to introduce another type :)

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

回答by Holger

As already discussed, the API designers do not assume that the developer wants to treat nullvalues and absent values the same way.

正如已经讨论过的,API 设计者并不假设开发人员希望以null相同的方式对待值和缺失值。

If you still want to do that, you may do it explicitly by applying the sequence

如果你仍然想这样做,你可以通过应用序列来明确地做到这一点

.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

到溪流。在这两种情况下,如果没有第一个元素或第一个元素是 ,结果将是一个空的可选项null。所以在你的情况下,你可以使用

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

to get a nullvalue if the first element is either absent or null.

得到一个null值,如果第一个元素是不存在或null

If you want to distinguish between these cases, you may simply omit the flatMapstep:

如果你想区分这些情况,你可以简单地省略这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:

这与您更新的问题没有太大不同。你只需要更换"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);

Now, firstStringwill be nullif an element exists but is nulland it will be "StringWhenListIsEmpty"when no element exists.

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

回答by Nathan

The following code replaces findFirst()with limit(1)and replaces orElse()with reduce():

以下代码替换findFirst()limit(1)和替换orElse()reduce()

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

limit()allows only 1 element to reach reduce. The BinaryOperatorpassed to reducereturns that 1 element or else "StringWhenListIsEmpty"if no elements reach the reduce.

limit()只允许 1 个元素到达reduce。在BinaryOperator传递到reduce返回的是1元,否则"StringWhenListIsEmpty",如果没有元素到达reduce

The beauty of this solution is that Optionalisn't allocated and the BinaryOperatorlambda isn't going to allocate anything.

这个解决方案的美妙之处在于Optional它没有被分配,而且BinaryOperatorlambda 不会分配任何东西。

回答by Mattos

You can use java.util.Objects.nonNullto filter the list before find

您可以java.util.Objects.nonNull在查找之前使用过滤列表

something like

就像是

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