Java的Collectors.toSet()是否保证允许空值?

时间:2017-11-09 22:48:20

标签: java java-8 null java-stream

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()。此外,该问题的答案形成了我的问题的基本假设。那个问题问:流中是否允许空值?是。 但是当通过标准收集器收集包含空值的(完全允许的)流时会发生什么?

1 个答案:

答案 0 :(得分:5)

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

每当一个行为被指定时,参考实现的实际行为往往成为事实的问题,即使由于兼容性限制,或者至少它不能在以后也不能改变,即使抵消了初衷没有充分理由就改变。

请注意,虽然未使用返回真正不可变或不可序列化Set的保留权限,但仅仅因为Java 8版本中不存在此类型,强制执行非null行为即使没有足够的哈希映射类型也可能,就像groupingBy禁止null键一样,尽管也未指定。

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

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