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

Cho*_*s-2 11 java null java-8 java-stream

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

Collectors.toSet()承诺回报的实现Set,但明确地让"无担保的类型,可变性,串行化,或线程安全Set返回".没有提到空安全性.

目前执行Collectors.toSet()中的OpenJDK总是使用HashSet,允许null元素,但是这可能在未来改变和其他实施方式可以做出不同的.

如果某个Set实现禁止null元素,它会NullPointerException在不同时间抛出,特别是在尝试时add(null).似乎如果Collectors.toSet()决定使用null不容忍的Set实现,则调用stream.collect(Collectors.toSet())一个Stream stream将抛出.规范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);
    }
}
Run Code Online (Sandbox Code Playgroud)

如果没有,那么我假设在使用Collectors.toSet()或准备好处理之前我们应该始终确保流不包含空值NullPointerException.(这是例外单独够有关系吗?)或者,当这是不可接受的或硬的,我们可以要求使用类似的代码一组特定的实现Collectors.toCollection(HashSet::new).

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

Hol*_*ger 5

故意未指定的行为(如"类型,可变性,可串行性或线程安全性")和未指定的行为(如支持)之间存在差异null.

每当一个行为被指定时,参考实现的实际行为往往成为事实的问题,即使由于兼容性限制而抵消原始意图,以后也无法改变,或者至少如果没有一个有力的理由.

请注意,虽然Set未使用返回真正不可变或不可序列化的保留权限,但仅仅因为Java 8发行版中null不存在此类型,即使没有足够的哈希映射类型,也可以强制执行非行为,比如groupingBy禁止null密钥,但也没有说明.

进一步注意,虽然groupingBy收集器故意拒绝null其实现代码中的密钥,但是toMap实际行为如何成为合同的一部分的一个很好的例子.在Java 8中,toMap允许null键但拒绝null值,只是因为它调用Map.merge具有该行为的键.看来,这首先不是预期的行为.现在,在Java 9中,toMap没有合并功能的收集器不再使用Map.merge(JDK-8040892,请参阅此答案),但故意拒绝null收集器代码中的值,以与先前版本行为兼容.仅仅因为从未说过该null行为是故意未指明的.

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

  • +1 Btw,新的Java 10`toUnmodifiable`收集器不允许空值.请参阅https://bugs.openjdk.java.net/browse/JDK-8184690 (3认同)