【问题标题】:Why does findFirst() throw a NullPointerException if the first element it finds is null?如果 findFirst() 找到的第一个元素为 null,为什么会抛出 NullPointerException?
【发布时间】:2015-12-04 16:04:43
【问题描述】:

为什么会抛出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() 返回一个Optional,这让findFirst() 能够处理nulls 更有意义。

编辑:更新了 orElse() 以减少歧义。

【问题讨论】:

  • null 不是完全可以接受的值...改用 ""
  • @MicheleLacorte 虽然我在这里使用String,但如果它是一个代表数据库中列的列表怎么办?该列第一行的值可以是null
  • 是的,但在 java 中 null 是不可接受的。使用查询将 null 设置为 db
  • @MicheleLacorte, null 在 Java 中是一个完全可以接受的值,一般来说。特别是,它是ArrayList&lt;String&gt; 的有效元素。然而,与任何其他值一样,可以用它做什么也是有限制的。 “永远不要使用null”不是有用的建议,因为你无法避免它。
  • @NathanHughes - 我怀疑当你打电话给findFirst() 时,你已经没有什么想做的了。

标签: java java-8 java-stream optional


【解决方案1】:

您可以在查找之前使用java.util.Objects.nonNull过滤列表

类似

list.stream().filter(Objects::nonNull).findFirst();

【讨论】:

  • 如果strings 中的第一项是null,我希望firstString 成为null
  • 不幸的是它使用了Optional.of 这不是空安全的。你可以 mapOptional.ofNullable 然后使用 findFirst 但你最终会得到一个 Optional 的 Optional
【解决方案2】:

以下代码将findFirst()替换为limit(1),将orElse()替换为reduce()

String firstString = strings.
   stream().
   limit(1).
   reduce("StringWhenListIsEmpty", (first, second) -> second);

limit() 只允许 1 个元素到达 reduce。传递给reduceBinaryOperator 返回该1 个元素,如果没有元素到达reduce,则返回"StringWhenListIsEmpty"

这个解决方案的美妙之处在于 Optional 没有分配,BinaryOperator lambda 也不会分配任何东西。

【讨论】:

    【解决方案3】:

    作为already discussed,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"

    【讨论】:

    • 对不起,我意识到我的问题可能暗示我想返回 null 1) 第一个元素是 null 或 2) 列表中不存在任何元素。我已更新问题以消除歧义。
    • 在第三个代码 sn-p 中,Optional 可以分配给 null。由于Optional 应该是“值类型”,所以它永远不应该为空。 == 永远不应该比较 Optional。 Java 10 中的代码可能会失败 :) 或者任何时候将值类型引入 Java。
    • @bayou.io: the documentation 并没有说对值类型的 references 不能是 nullinstances 应该永远不会被== 比较,可以使用== 测试null 的引用,因为这是测试null唯一方法。我看不出这种到“从不null”的转换应该如何适用于现有代码,因为即使所有实例变量和数组元素的默认值都是null。 sn-p 肯定不是最好的代码,但将nulls 视为当前值的任务也不是。
    • 参见john rose - 也不能与“==”运算符进行比较,甚至不能与空值进行比较
    • 由于此代码使用通用 API,这就是概念所称的 boxed 表示形式,可以是 null。但是,由于这种假设的语言更改会导致编译器在这里发出错误(而不是静默地破坏代码),我可以接受这样一个事实,即它可能必须适应 Java 10。我想,StreamAPI 看起来也会很不一样……
    【解决方案4】:

    Optional 应该是一个“值”类型。 (阅读javadoc 中的细则:)JVM 甚至可以用Foo 替换所有Optional&lt;Foo&gt;,从而消除所有装箱和拆箱成本。 null Foo 表示空的 Optional&lt;Foo&gt;

    一种可能的设计是允许具有空值的 Optional,而不添加布尔标志 - 只需添加一个哨兵对象。 (甚至可以使用 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 问题的解决方案是引入另一种类型 :)

    【讨论】:

    • 不需要创建另一个Box 类型。 Optional 类型本身可以达到这个目的。有关示例,请参见 my answer
    • @Holger - 是的,但这可能会令人困惑,因为这不是 Optional 的预期目的。在 OP 的情况下,null 与其他任何值一样是一个有效值,没有对其进行特殊处理。 (直到某个时候:)
    【解决方案5】:

    原因是在返回中使用了Optional&lt;T&gt;。 Optional 不允许包含null。本质上,它无法区分“它不存在”和“它存在,但它设置为null”两种情况。

    这就是为什么the documentation明确禁止在findFirst()中选择null的情况:

    投掷:

    NullPointerException - 如果选择的元素是null

    【讨论】:

    • 如果在Optional 的实例中存在私有布尔值,则可以很容易地跟踪该值。无论如何,我认为我在咆哮 - 如果语言不支持它,它不支持它。
    • @neverendingqs 当然,使用boolean 来区分这两种情况非常有意义。对我来说,在这里使用Optional&lt;T&gt; 似乎是一个值得商榷的选择。
    • @neverendingqs 除了rolling your own null,我想不出任何好看的替代品,这也不理想。
    • 我最终编写了一个私有方法,从任何Iterable 类型获取迭代器,检查hasNext(),并返回适当的值。
    • 我认为如果 findFirst 在 PO 的情况下返回一个空的 Optional 值会更有意义
    猜你喜欢
    • 1970-01-01
    • 2011-03-09
    • 2014-12-18
    • 2014-07-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多