【问题标题】:Why is findFirst() throwing a NullPointerException if I'm priorly filtering only for present() values?如果我事先只过滤 present() 值,为什么 findFirst() 会抛出 NullPointerException?
【发布时间】:2015-11-10 09:23:42
【问题描述】:

我有一个 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()-方法调用。

【问题讨论】:

  • 您能否提供一个包含一些虚拟数据的可重现示例?
  • 请提供异常的整个堆栈跟踪。我强烈怀疑 NPE 的真正原因不是findFirst(但可能是s -&gt; s.contains(...) lambda)。
  • @TagirValeev 这也是我的第一个想法。编写大量单行代码存在问题 - 调试起来有点困难......
  • 附带说明,如果您只有两个列表,Stream.concat(a.stream(), b.stream())Stream.of(a, b).flatmap(Collection::stream) 更简洁
  • @Blacklight,closevotes 的意思是“这个问题对更多的读者没有用”。你不应该担心他们,因为你已经解决了你的问题。实际上,如果您从一开始就发布堆栈跟踪,事情就会简单得多。现在我重新投票,因为我可以在答案中解释如何正确读取 Stream API 调用中出现的异常的堆栈跟踪。但我怀疑你会收集到足够多的重新开票。请记住未来:如果您问“为什么我看到这样的异常”,总是发布堆栈跟踪。

标签: java java-8 java-stream optional


【解决方案1】:

读取出现在 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)

如果您在跟踪中有一些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)

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

在您的案例中,最顶层的堆栈帧读作com.example.services.impl.RestServiceImpl$$Lambda$11/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$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 -&gt; c.stream()) 行。

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

总结一下:

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

【讨论】:

  • 优秀的答案,由于广泛的解释而被接受。
【解决方案2】:

我发现了错误,评论者是正确的:问题不是Optional,而是源列表!如果未找到密钥,HttpHeaders.get(Object key) 返回 null。我错误地认为null-lists 没有被收集,或者返回的是空列表而不是null。如果我对此进行过滤(或事先检查标题是否存在),它会按预期工作。

谢谢你指点我!我写了一个小例子,向有兴趣的人展示这个问题:

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();
    }
}

【讨论】:

    猜你喜欢
    • 2015-12-04
    • 2014-01-24
    • 2014-12-18
    • 2014-07-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多