如果我之前只过滤 present() 值,为什么 findFirst() 会抛出 NullPointerException?
Why is findFirst() throwing a NullPointerException if I'm priorly filtering only for present() values?
我有 Stream
个字符串,并且正在将每个字符串映射到 Optional<String>
。由于我之后过滤空Optionals
,返回的流应该只包含非空Optionals
持有非空字符串。
为什么 findFirst()
会抛出一个 NullPointerException
?
Optional<String> cookie =
Stream.of(headers.get(HttpHeaders.SET_COOKIE), headers.get(HttpHeaders.COOKIE))
.flatMap(Collection::stream)
.filter(s -> s.contains("identifier"))
.map(this::parseCookieValue) //returns an Optional<String> from Optional.ofNullable(), null-values should result in empty Optionals
.filter(Optional::isPresent) // filters out non-present values
.map(Optional::get) // all Optionals here should have values
.findFirst(); // so why is this still throwing a NullPointerException?
堆栈跟踪:
Caused by: java.lang.NullPointerException
at com.example.services.impl.RestServiceImpl$$Lambda/873175411.apply(Unknown Source)
at java.util.stream.ReferencePipeline.accept(ReferencePipeline.java:267)
at java.util.Spliterators$ArraySpliterator.tryAdvance(Spliterators.java:958)
at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126)
at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:529)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:516)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502)
at java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:152)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:464)
at com.example.services.impl.RestServiceImpl.login(RestServiceImpl.java:81)
第 81 行是 findFirst()
方法调用。
我发现了错误,评论者是正确的:问题不是 Optional
,而是来源列表! HttpHeaders.get(Object key) returns null
如果找不到密钥。我错误地假设 null
-列表未被收集,或者返回的是空列表而不是 null
。如果我对此进行过滤(或事先检查 headers 是否存在),它会按预期工作。
感谢您指点我!我写了一个小例子来为感兴趣的人演示这个问题:
package com.example;
import java.util.*;
import java.util.stream.Stream;
public class Main {
public static void main(String[] args) {
succeeds();
fixed();
fails();
}
private static void succeeds() {
List<String> list1 = Collections.singletonList("identifier=xxx");
List<String> list2 = Collections.emptyList();
Optional<String> cookieValue =
Stream.of(list1, list2)
.flatMap(Collection::stream)
.filter(s -> s.contains("identifier"))
.map(Main::parseCookieValue)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
System.out.println(cookieValue.orElse("Code works as expected with non-null Lists"));
}
private static void fails() {
List<String> list1 = Collections.singletonList("identifier=xxx");
List<String> list2 = null;
Optional<String> cookieValue =
Stream.of(list1, list2)
.flatMap(Collection::stream)
.filter(s -> s.contains("identifier"))
.map(Main::parseCookieValue)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
System.out.println(cookieValue.orElse("Exception thrown prior to this call!"));
}
private static void fixed() {
List<String> list1 = Collections.singletonList("identifier=xxx");
List<String> list2 = null;
Optional<String> cookieValue =
Stream.of(list1, list2)
.filter(l -> l != null)
.flatMap(Collection::stream)
.filter(s -> s.contains("identifier"))
.map(Main::parseCookieValue)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
System.out.println(cookieValue.orElse("Code works as expected after null Lists have been filtered"));
}
private static Optional<String> parseCookieValue(final String headerString) {
System.out.println("Parsing method called");
//return an empty Optional for testing;
return Optional.empty();
}
}
流中出现的读取异常 API 并非微不足道。首先你不应该忘记 Stream 是惰性的:一切实际上都是在终端操作中执行的。因此,在您的情况下,整个 Stream 处理都在 findFirst
调用内执行,如果您看到 NullPointerException
它可以由管道的任何步骤生成,而不仅仅是 findFirst
本身。让我们仔细看看stacktrace的顶部:
Caused by: java.lang.NullPointerException
at com.example.services.impl.RestServiceImpl$$Lambda/873175411.apply(Unknown Source)
at java.util.stream.ReferencePipeline.accept(ReferencePipeline.java:267)
at java.util.Spliterators$ArraySpliterator.tryAdvance(Spliterators.java:958)
at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126)
如果您在跟踪中有一些 Spliterator.tryAdvance
或 Spliterator.forEachRemaining
调用,那么异常实际上发生在某些流元素的处理过程中,而不是在最终操作过程中。下面是如果您实际将空值传递给 findFirst
:
时异常的样子
Exception in thread "main" java.lang.NullPointerException
at java.util.Objects.requireNonNull(Objects.java:203)
at java.util.Optional.<init>(Optional.java:96)
at java.util.Optional.of(Optional.java:108)
at java.util.stream.FindOps$FindSink$OfRef.get(FindOps.java:193)
at java.util.stream.FindOps$FindSink$OfRef.get(FindOps.java:190)
at java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:152)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:464)
看,这里没有 spliterator 调用:它完成了每个元素的处理,然后抛出。
您案例中最顶层的堆栈框架显示为 com.example.services.impl.RestServiceImpl$$Lambda/873175411.apply
。自动生成的 lambda 中的 NullPointerException
不指向任何已知代码通常意味着未绑定的方法引用被调用为 null this
参数。为了使这一点更清楚,您可以将代码中的所有方法引用替换为 lambda,因为它们实际上有一个源代码行:
Optional<String> cookie =
Stream.of(headers.get(HttpHeaders.SET_COOKIE), headers.get(HttpHeaders.COOKIE))
.flatMap(c -> c.stream())
.filter(s -> s.contains("identifier"))
.map(c -> this.parseCookieValue(c))
.filter(opt -> opt.isPresent())
.map(opt -> opt.get())
.findFirst();
现在您将看到带有行号的附加帧:
Exception in thread "main" java.lang.NullPointerException
at com.example.services.impl.RestServiceImpl.lambda[=13=](RestServiceImpl.java:14)
at com.example.services.impl.RestServiceImpl$$Lambda/2055281021.apply(Unknown Source)
at java.util.stream.ReferencePipeline.accept(ReferencePipeline.java:267)
at java.util.Spliterators$ArraySpliterator.tryAdvance(Spliterators.java:958)
at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126)
此行号正好指向显示异常原因的 .flatMap(c -> c.stream())
行。
如果您不想将所有可疑的方法引用都转换为 lambda,查看上一帧可能会有线索 (ReferencePipeline.java:267)。 JDK source appears inside the flatMap
implementation 中的这一行,因此您可能会得出结论,在 flatMap
步骤中发生了错误。
总结一下:
- 如果您看到涉及终端 Stream 操作的异常,它实际上可能发生在您的 Stream 的任何阶段。
- 执行每个元素处理时,您可能会在跟踪中看到
tryAdvance
或 forEachRemaining
spliterator 方法调用。当您看不到它时,很可能每个元素的处理已经完成或尚未开始。
- 首先检查最顶层的框架:它可能指向实际发生异常的 lambda 主体。
- 如果最顶层的框架有点神秘/有 "Unknown Source",您可能尝试将方法引用绑定到空指针。在这种情况下,用 lambda 替换方法引用可能有助于理解发生了什么。
- 不要害怕查看 Stream API 来源。它也可能提供线索。
我有 Stream
个字符串,并且正在将每个字符串映射到 Optional<String>
。由于我之后过滤空Optionals
,返回的流应该只包含非空Optionals
持有非空字符串。
为什么 findFirst()
会抛出一个 NullPointerException
?
Optional<String> cookie =
Stream.of(headers.get(HttpHeaders.SET_COOKIE), headers.get(HttpHeaders.COOKIE))
.flatMap(Collection::stream)
.filter(s -> s.contains("identifier"))
.map(this::parseCookieValue) //returns an Optional<String> from Optional.ofNullable(), null-values should result in empty Optionals
.filter(Optional::isPresent) // filters out non-present values
.map(Optional::get) // all Optionals here should have values
.findFirst(); // so why is this still throwing a NullPointerException?
堆栈跟踪:
Caused by: java.lang.NullPointerException
at com.example.services.impl.RestServiceImpl$$Lambda/873175411.apply(Unknown Source)
at java.util.stream.ReferencePipeline.accept(ReferencePipeline.java:267)
at java.util.Spliterators$ArraySpliterator.tryAdvance(Spliterators.java:958)
at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126)
at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:529)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:516)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502)
at java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:152)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:464)
at com.example.services.impl.RestServiceImpl.login(RestServiceImpl.java:81)
第 81 行是 findFirst()
方法调用。
我发现了错误,评论者是正确的:问题不是 Optional
,而是来源列表! HttpHeaders.get(Object key) returns null
如果找不到密钥。我错误地假设 null
-列表未被收集,或者返回的是空列表而不是 null
。如果我对此进行过滤(或事先检查 headers 是否存在),它会按预期工作。
感谢您指点我!我写了一个小例子来为感兴趣的人演示这个问题:
package com.example;
import java.util.*;
import java.util.stream.Stream;
public class Main {
public static void main(String[] args) {
succeeds();
fixed();
fails();
}
private static void succeeds() {
List<String> list1 = Collections.singletonList("identifier=xxx");
List<String> list2 = Collections.emptyList();
Optional<String> cookieValue =
Stream.of(list1, list2)
.flatMap(Collection::stream)
.filter(s -> s.contains("identifier"))
.map(Main::parseCookieValue)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
System.out.println(cookieValue.orElse("Code works as expected with non-null Lists"));
}
private static void fails() {
List<String> list1 = Collections.singletonList("identifier=xxx");
List<String> list2 = null;
Optional<String> cookieValue =
Stream.of(list1, list2)
.flatMap(Collection::stream)
.filter(s -> s.contains("identifier"))
.map(Main::parseCookieValue)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
System.out.println(cookieValue.orElse("Exception thrown prior to this call!"));
}
private static void fixed() {
List<String> list1 = Collections.singletonList("identifier=xxx");
List<String> list2 = null;
Optional<String> cookieValue =
Stream.of(list1, list2)
.filter(l -> l != null)
.flatMap(Collection::stream)
.filter(s -> s.contains("identifier"))
.map(Main::parseCookieValue)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
System.out.println(cookieValue.orElse("Code works as expected after null Lists have been filtered"));
}
private static Optional<String> parseCookieValue(final String headerString) {
System.out.println("Parsing method called");
//return an empty Optional for testing;
return Optional.empty();
}
}
流中出现的读取异常 API 并非微不足道。首先你不应该忘记 Stream 是惰性的:一切实际上都是在终端操作中执行的。因此,在您的情况下,整个 Stream 处理都在 findFirst
调用内执行,如果您看到 NullPointerException
它可以由管道的任何步骤生成,而不仅仅是 findFirst
本身。让我们仔细看看stacktrace的顶部:
Caused by: java.lang.NullPointerException
at com.example.services.impl.RestServiceImpl$$Lambda/873175411.apply(Unknown Source)
at java.util.stream.ReferencePipeline.accept(ReferencePipeline.java:267)
at java.util.Spliterators$ArraySpliterator.tryAdvance(Spliterators.java:958)
at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126)
如果您在跟踪中有一些 Spliterator.tryAdvance
或 Spliterator.forEachRemaining
调用,那么异常实际上发生在某些流元素的处理过程中,而不是在最终操作过程中。下面是如果您实际将空值传递给 findFirst
:
Exception in thread "main" java.lang.NullPointerException
at java.util.Objects.requireNonNull(Objects.java:203)
at java.util.Optional.<init>(Optional.java:96)
at java.util.Optional.of(Optional.java:108)
at java.util.stream.FindOps$FindSink$OfRef.get(FindOps.java:193)
at java.util.stream.FindOps$FindSink$OfRef.get(FindOps.java:190)
at java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:152)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:464)
看,这里没有 spliterator 调用:它完成了每个元素的处理,然后抛出。
您案例中最顶层的堆栈框架显示为 com.example.services.impl.RestServiceImpl$$Lambda/873175411.apply
。自动生成的 lambda 中的 NullPointerException
不指向任何已知代码通常意味着未绑定的方法引用被调用为 null this
参数。为了使这一点更清楚,您可以将代码中的所有方法引用替换为 lambda,因为它们实际上有一个源代码行:
Optional<String> cookie =
Stream.of(headers.get(HttpHeaders.SET_COOKIE), headers.get(HttpHeaders.COOKIE))
.flatMap(c -> c.stream())
.filter(s -> s.contains("identifier"))
.map(c -> this.parseCookieValue(c))
.filter(opt -> opt.isPresent())
.map(opt -> opt.get())
.findFirst();
现在您将看到带有行号的附加帧:
Exception in thread "main" java.lang.NullPointerException
at com.example.services.impl.RestServiceImpl.lambda[=13=](RestServiceImpl.java:14)
at com.example.services.impl.RestServiceImpl$$Lambda/2055281021.apply(Unknown Source)
at java.util.stream.ReferencePipeline.accept(ReferencePipeline.java:267)
at java.util.Spliterators$ArraySpliterator.tryAdvance(Spliterators.java:958)
at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126)
此行号正好指向显示异常原因的 .flatMap(c -> c.stream())
行。
如果您不想将所有可疑的方法引用都转换为 lambda,查看上一帧可能会有线索 (ReferencePipeline.java:267)。 JDK source appears inside the flatMap
implementation 中的这一行,因此您可能会得出结论,在 flatMap
步骤中发生了错误。
总结一下:
- 如果您看到涉及终端 Stream 操作的异常,它实际上可能发生在您的 Stream 的任何阶段。
- 执行每个元素处理时,您可能会在跟踪中看到
tryAdvance
或forEachRemaining
spliterator 方法调用。当您看不到它时,很可能每个元素的处理已经完成或尚未开始。 - 首先检查最顶层的框架:它可能指向实际发生异常的 lambda 主体。
- 如果最顶层的框架有点神秘/有 "Unknown Source",您可能尝试将方法引用绑定到空指针。在这种情况下,用 lambda 替换方法引用可能有助于理解发生了什么。
- 不要害怕查看 Stream API 来源。它也可能提供线索。