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

2022-09-03 15:46:58

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

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

OpenJDK 中 的当前实现始终使用 ,这允许空元素,但这将来可能会更改,其他实现可能会有所不同。Collectors.toSet()HashSet

如果一个实现禁止元素,它会在不同的时间抛出,特别是在尝试 add(null) 期间。看起来,如果决定使用空不容许的实现,在 a 上调用 stream.collect(Collectors.toSet()) 会抛出。的规范未列出任何异常,任何收集器方法的规范也未列出。这可能表明调用允许在 中空值,但另一方面,尚不清楚这实际上是否意味着很多,因为这是一个未经检查的例外,并且不必严格列出。SetnullNullPointerExceptionCollectors.toSet()SetStream streamcollectcollectstreamNullPointerException

这是否在其他任何地方都更清楚地指定?特别是,以下代码是否保证不会抛出?它保证返回吗?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()NullPointerExceptionCollectors.toCollection(HashSet::new)

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


答案 1

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

每当行为被低估时,参考实现的实际行为往往会成为以后无法更改的事实,即使由于兼容性约束而抵消了初衷,或者至少在没有强烈理由的情况下无法更改。

请注意,虽然没有使用返回真正不可变或不可序列化的保留权利,仅仅是因为在Java 8发行版上不存在这样的类型,但即使不存在足够的哈希映射类型,也可以强制执行非行为,就像禁止键一样,尽管也指定不足。SetnullgroupingBynull

进一步注意,虽然收集器故意拒绝其实现代码中的键,但这是实际行为如何成为协定一部分的一个很好的例子。在 Java 8 中,允许键但拒绝值,仅仅是因为它调用了具有该行为的值。看起来,这本来就不是一个预期的行为。 现在,在 Java 9 中,没有合并函数的收集器不再使用(JDK-8040892,另请参阅此答案),而是故意拒绝收集器代码中的值,以便与以前的版本在行为上兼容。仅仅因为从未说过该行为是故意未指定的。groupingBynulltoMaptoMapnullnullMap.mergetoMapMap.mergenullnull

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


答案 2