java - 如果我之前只过滤 present() 值,为什么 findFirst() 会抛出 NullPointerException?

标签 java java-8 java-stream option-type

我有一个 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$11/873175411.apply(Unknown Source)
    at java.util.stream.ReferencePipeline$7$1.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() -方法调用。

最佳答案

出现在 Stream API 中的读取异常并非微不足道。首先你不应该忘记 Stream 是惰性的:一切实际上都是在终端操作中执行的。因此,在您的情况下,整个 Stream 处理是在 findFirst 调用中执行的,如果您看到 NullPointerException 它可以由管道的任何步骤产生,而不仅仅是 findFirst 本身。让我们仔细看看堆栈跟踪的顶部:

Caused by: java.lang.NullPointerException
    at com.example.services.impl.RestServiceImpl$$Lambda$11/873175411.apply(Unknown Source)
    at java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:267)
    at java.util.Spliterators$ArraySpliterator.tryAdvance(Spliterators.java:958)
    at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126)

如果跟踪中有一些 Spliterator.tryAdvanceSpliterator.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$11/873175411.apply。自动生成的 lambda 中的 NullPointerException 不指向任何已知代码,这通常意味着未绑定(bind)的方法引用是为 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$0(RestServiceImpl.java:14)
    at com.example.services.impl.RestServiceImpl$$Lambda$1/2055281021.apply(Unknown Source)
    at java.util.stream.ReferencePipeline$7$1.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源中的这一行appearsflatMap 实现中,因此您可能会得出结论,在 flatMap 步骤中发生了错误。

总结一下:

  • 如果您看到涉及终端 Stream 操作的异常,它实际上可能发生在您的 Stream 的任何阶段。
  • 执行按元素处理时,您可能会在跟踪中看到 tryAdvanceforEachRemaining 拆分器方法调用。当您看不到它时,很可能每个元素的处理已经完成或尚未开始。
  • 首先检查最顶层的框架:它可能指向实际发生异常的 lambda 主体。
  • 如果最顶层的框架有点神秘/有“未知来源”,则可能是您尝试将方法引用绑定(bind)到空指针。在这种情况下,用 lambda 替换方法引用可能有助于理解发生了什么。
  • 不要害怕查看 Stream API 源代码。它还可能提供线索。

关于java - 如果我之前只过滤 present() 值,为什么 findFirst() 会抛出 NullPointerException?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33626616/

相关文章:

java - 集合成为原始类型

java - Java SE 7 何时发布?

java - 如何将时间戳转换为不同时区

Java 8 分组依据的逆向

java - 什么是NullPointerException,我该如何解决?

java - Android AsyncTask 出现问题 - JavaMail

映射后的Java流到数组

java - 在 Java 8+ 中对 map 进行分区

java - 关闭 Java 8 JDK 安装的自动更新

java - 尝试使用多个资源会导致 Sonar qube 问题