【问题标题】:Is Java’s Collectors.toSet() guaranteed to permit nulls?Java 的 Collectors.toSet() 是否保证允许空值?
【发布时间】:2017-11-09 22:48:19
【问题描述】:

Set 接口不承诺实现是否允许null 元素。每个实现都应该在其文档中声明这一点。

Collectors.toSet() 承诺返回 Set 的实现,但明确“不保证返回的 Set 的类型、可变性、可序列化性或线程安全性”。没有提到空安全。

OpenJDK 中Collectors.toSet() 的当前实现始终使用HashSet,它允许空元素,但这可能会在未来发生变化,其他实现可能会有所不同。

如果Set 实现禁止null 元素,它会在不同时间抛出NullPointerException,尤其是在尝试add(null) 期间。似乎如果Collectors.toSet() 决定使用不容忍空值的Set 实现,则在Stream stream 上调用stream.collect(Collectors.toSet()) 会抛出异常。 collect 的规范没有列出任何例外,也没有列出任何 Collector 方法的规范。这可能表明collect 调用允许stream 中的空值,但另一方面,不清楚这是否真的意味着很多,因为NullPointerException 是一个未经检查的异常,并且不必严格列出。

这在其他地方有更清楚的说明吗?特别是,下面的代码是否保证不抛出?是否保证返回true

import java.util.stream.*;

class Test {
    public static boolean setContainsNull() {
        return Stream.of("A", "list", "of", null, "strings")
                     .collect(Collectors.toSet())
                     .contains(null);
    }
}

如果不是,那么我假设我们应该在使用Collectors.toSet() 之前始终确保流不包含空值,或者准备好处理NullPointerException。 (但仅此异常是否足够?)或者,当这是不可接受或难以接受时,我们可以使用 Collectors.toCollection(HashSet::new) 之类的代码请求特定的集合实现。

编辑: 有一个existing question 听起来表面上相似,这个问题作为假设的重复而被关闭。但是,链接的问题根本没有解决Collectors.toSet()。此外,该问题的答案构成了我的问题的基本假设。这个问题问:流中是否允许空值?是的。 但是当包含空值的(完全允许的)流通过标准收集器收集时会发生什么?

【问题讨论】:

  • 没有提到,但我认为如果它不允许空值,那将是非常糟糕的形式,并且没有说明任何内容。
  • @SleimanJneidi 您链接的问题与标准收集器无关,这就是我要问的问题。它询问流是否可以包含空值(它们确实存在,我的问题认为这是理所当然的)以及是否有任何魔法可以让明确地不容忍空值的代码应用于包含空值的流以避免 NPE(没有)。 collect(Collectors.toSet()) 不是明确的不容忍 null 的代码——或者如果你愿意,我只是在问它是否是。
  • 由于它没有指定,我不会假设任何一种方式。如果我知道空值是可能的,我会一直使用toCollection(HashSet::new),以确保完全安全。

标签: java java-8 null java-stream


【解决方案1】:

故意未指定的行为(例如“类型、可变性、可序列化性或线程安全”)与未指定的行为(例如 null 支持)之间存在差异。

每当一个行为未指定时,参考实现的实际行为往往会变成事后无法改变的事实,即使违背了初衷,由于兼容性限制,或者至少不能无正当理由更改。

请注意,虽然没有使用返回真正不可变或不可序列化 Set 的保留权利,只是因为在 Java 8 版本中不存在此类类型,因此即使没有存在适当的哈希映射类型,就像 groupingBy 禁止 null 键一样,尽管也未指定。

进一步注意,虽然groupingBy 收集器在其实现代码中故意拒绝null 键,但toMap 是一个很好的例子,说明实际行为如何成为合同的一部分。在 Java 8 中,toMap 允许 null 键但拒绝 null 值,仅仅是因为它调用具有该行为的 Map.mergeIt seems, this wasn’t an intended behavior in the first place. 现在,在 Java 9 中,没有合并功能的 toMap 收集器不再使用 Map.mergeJDK-8040892,另请参见 this answer),但故意拒绝收集器代码中的 null 值,与以前的版本在行为上兼容。仅仅是因为从来没有说过 null 行为是故意未指定的。

所以,Collectors.toSet()(以及同样的Collectors.toList())现在允许两个主要 Java 版本使用 null 值,并且没有规范说你不能认为这是理所当然的,所以你可以很确定这不会'未来不会改变。

【讨论】:

猜你喜欢
  • 2016-01-26
  • 2011-04-12
  • 2013-12-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-20
相关资源
最近更新 更多