如果 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 个元素到达 reduce
。 BinaryOperator
传递给 reduce
returns 那 1 个元素,否则 "StringWhenListIsEmpty"
如果没有元素到达 reduce
。
这个解决方案的美妙之处在于 Optional
没有分配并且 BinaryOperator
lambda 不会分配任何东西。
您可以在查找
之前使用java.util.Objects.nonNull
过滤列表
类似于
list.stream().filter(Objects::nonNull).findFirst();
为什么会抛出 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 isnull
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 问题的方法是引入另一种类型:)
与 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 个元素到达 reduce
。 BinaryOperator
传递给 reduce
returns 那 1 个元素,否则 "StringWhenListIsEmpty"
如果没有元素到达 reduce
。
这个解决方案的美妙之处在于 Optional
没有分配并且 BinaryOperator
lambda 不会分配任何东西。
您可以在查找
之前使用java.util.Objects.nonNull
过滤列表
类似于
list.stream().filter(Objects::nonNull).findFirst();