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

Bla*_*ght 3 java optional java-8 java-stream

我有一个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?
Run Code Online (Sandbox Code Playgroud)

堆栈跟踪:

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)
Run Code Online (Sandbox Code Playgroud)

第81行是findFirst()- 方法调用.

Tag*_*eev 11

读取Stream API中出现的异常并非易事.你不应该忘记的第一件事是Stream是懒惰的:一切都是在终端操作中实际执行的.因此,在您的情况下,整个Stream处理在findFirst调用内部执行,如果您看到NullPointerException它可以由管道的任何步骤生成,而不仅仅是findFirst它自己.让我们仔细看看stacktrace的顶部:

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)
Run Code Online (Sandbox Code Playgroud)

如果您有一些Spliterator.tryAdvanceSpliterator.forEachRemaining调用跟踪,则在处理某些流元素期间实际发生了异常,而不是在最终操作期间.如果您实际将null值传递给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)
Run Code Online (Sandbox Code Playgroud)

看,这里没有spliterator调用:它完成了每个元素的处理并在此之后抛出.

您案例中最顶层的堆栈框如下所示com.example.services.impl.RestServiceImpl$$Lambda$11/873175411.apply.的NullPointerException内部自动生成的拉姆达不指向任何已知代码通常意味着未结合的方法参考被调用空this参数.为了使这一点更清楚,您可以使用lambdas替换代码中的所有方法引用,因为它们实际上有一个源代码行:

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();
Run Code Online (Sandbox Code Playgroud)

现在,您将看到带有行号的附加框架:

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)
Run Code Online (Sandbox Code Playgroud)

此行号指向.flatMap(c -> c.stream())显示异常原因的行.

如果您不想将所有可疑方法引用转换为lambdas,您可能会有一个线索查看前一帧(ReferencePipeline.java:267).JDK源代码中的这一行出现flatMap实现中,因此您可以得出结论,该flatMap步骤发生了错误.

总结一下:

  • 如果您看到涉及终端Stream操作的异常,它实际上可能发生在Stream的任何阶段.
  • 执行每元素处理时,您可能会在跟踪中看到tryAdvance或者使用forEachRemainingspliterator方法调用.当您没有看到它时,每个元素处理可能已经完成或未启动.
  • 首先检查最上面的框架:它可能指向lambda的实体,实际发生异常.
  • 如果最顶层的框架有些神秘/具有"未知源",则可能尝试将方法引用绑定到空指针.在这种情况下,用lambdas替换方法引用可能有助于理解发生了什么.
  • 不要害怕查看Stream API源代码.它也可能提供线索.