Set
接口不对实现是否允许null
元素做出承诺。每个实现都应该在其文档中声明这一点。
Collectors.toSet()
承诺返回Set
的实现,但明确地“不保证Set
返回的类型,可变性,可序列性或线程安全性”。没有提到无安全性。
OpenJDK中Collectors.toSet()
的当前实现始终使用HashSet
,它允许使用null元素,但这可能在将来发生变化,而其他实现可能会有所不同。
如果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()
。此外,该问题的答案形成了我的问题的基本假设。那个问题问:流中是否允许空值?是。 但是当通过标准收集器收集包含空值的(完全允许的)流时会发生什么?
答案 0 :(得分:5)
故意未指定的行为(如“类型,可变性,可序列化或线程安全”)与未指定的行为(如null
支持)之间存在差异。
每当一个行为被指定时,参考实现的实际行为往往成为事实的问题,即使由于兼容性限制,或者至少它不能在以后也不能改变,即使抵消了初衷没有充分理由就改变。
请注意,虽然未使用返回真正不可变或不可序列化Set
的保留权限,但仅仅因为Java 8版本中不存在此类型,强制执行非null
行为即使没有足够的哈希映射类型也可能,就像groupingBy
禁止null
键一样,尽管也未指定。
进一步注意,虽然groupingBy
收集器故意拒绝其实现代码中的null
个密钥,但toMap
是实际行为如何成为合同一部分的一个很好的例子。在Java 8中,toMap
允许null
个键,但拒绝null
值,只是因为它调用具有该行为的Map.merge
。 can be used to describe现在,在Java 9中,没有合并功能的toMap
收集器不再使用Map.merge
(It seems, this wasn’t an intended behavior in the first place. ,另请参阅JDK-8040892),但故意拒绝收集器代码中的null
值,与先前版本的行为兼容。仅仅因为从未说过null
行为是故意未指定的。
因此,Collectors.toSet()
(以及同样Collectors.toList()
)现在允许两个主要Java版本的null
值,并且没有规范说你不能认为这是理所当然的,所以你可以很确定这将来不会改变。