返回空的不可变集合而不是分配一个新的空集合有什么弊端?

时间:2019-06-16 10:00:50

标签: java collections immutability effective-java

我过去常常要求Collections.emptyList()emptySetemptyMap返回对不可变的空集合的引用,而不是从函数中返回null。这是因为我认为没有理由分配新的空集合实例:

private static Collection<Cheese> getCheesesOld() {
    if (cheesesInStock.isEmpty()) {
        return Collections.emptyList();
    }
    return new ArrayList<>(cheesesInStock);
}

今天,我在有效Java第三版的第54条中阅读了以下内容:

  

...您可以通过重复返回相同的 immutable 空集合来避免分配,因为不可变对象可以自由共享...   但是请记住,这是一种优化,很少需要这样做。如果您认为有需要,请在评估性能之前和之后确保其确实有用。

对于我来说,听起来我应该避免返回空的不可变集合,直到我证明它会提高性能。问题是:为什么?除了您不能对其进行突变并且要键入的代码更多之外,返回不可变的空集合还有其他缺点吗?

我是否应该更改默认行为并以防御性的方式返回内部列表的副本(第50项),即使该列表为空也不返回已经分配的Collections.emptyList()

3 个答案:

答案 0 :(得分:1)

Collections.emptyList()不分配内部数组,不能立即知道它们的大小,可能为它们编写了更好的Iterators / Spliterators(调用.stream()时的问题);另一方面,if分支可能比您从Collections.emptyList()获得的收益要昂贵。

这里通常的建议是-使用所有可行的方法,并为您轻松阅读。可能存在但不存在这种优化的一个地方是Collectors.toList():它总是返回一个空的ArrayList,而不是可能在Collections.emptyList()中返回java-8。我认为这样做是因为对这种很少使用的场景进行检查会在频繁使用的场景中引入惩罚。因此没有完成。

您还必须从书中拿出一些建议-那个人写了数以百万计的库(那里的建议非常好),但是您是否也这样做?

答案 1 :(得分:1)

  

但是请记住,是一种优化

我认为这里 this 是指避免新分配空集合的做法。如果您返回一个共享的空集合,那么它应该是不变的。但是,按照我阅读第54项的方式,Bloch表示,除非有证据表明分配新的空列表(new ArrayList<>())会损害性能,否则这样做就可以了。

答案 2 :(得分:1)

相反,我们可以看到它。显然,此方法的调用者不应假定可变列表,否则返回不可变列表将不起作用。

然后,在某些情况下返回实际上可变的ArrayList并没有强制不变性这一事实是一种优化。

考虑的不是很大

private static Collection<Cheese> getCheesesOld() {
    if(cheesesInStock.isEmpty()) {
        return Collections.emptyList();
    }
    return Collections.unmodifiableList(new ArrayList<>(cheesesInStock));
}

从性能或从代码开发/维护的角度来看,这只会增加少量开销。专门用于处理空列表的三行代码更为重要。

但是如果在列表为空的情况下返回三个新包装的对象,您可能仍会感到不舒服,如果您删除了特殊处理,则返回共享对象就足够了。即使您意识到这种情况几乎永远不会发生,即调用该方法时列表几乎永远不会为空,这种感觉也可能会持续。

很幸运,最近的Java版本提供了一个简单的解决方案。您可以使用

private static Collection<Cheese> getCheesesOld() {
    return List.copyOf(cheesesInStock);
}

使用Java 10或更高版本。返回的列表是不可变的,但不是包装在另一个保护对象中的列表,它将具有优化的版本,不仅适用于空列表,而且适用于JRE开发人员认为有益的小尺寸列表。

如果源已经是不可变的列表,那么它也足够聪明,不执行实际的复制操作,这意味着使用相同的方法创建传入列表的防御性副本将在它们已经是通过此类getter方法生成的副本时获得回报

您只需应付新的null处理,即彻底拒绝null元素。