返回空的不可变集合而不是分配新的空集合有什么缺点?



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

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

今天我读了《Effective Java》第3版第54项中的以下内容:

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

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

我是否应该更改我的默认行为并防御性地返回内部列表的副本(项目 50),即使它是空的,而不是返回已分配Collections.emptyList()

Collections.emptyList()

不分配内部数组,立即知道它们的大小,可能会为它们编写更好的迭代器/Spliterator(当你调用.stream()时很重要);另一方面,if分支可能比你从Collections.emptyList()中获得的收益更昂贵。

这里通常的建议是 - 使用任何对您来说有效且更容易阅读的东西。这种优化可能存在但不存在的一个地方是在Collectors.toList()中:它总是返回一个空ArrayList,而不是可能返回java-8中的Collections.emptyList()。我认为这样做是专门进行的,因为对这种很少使用的场景进行检查会在经常使用的场景中引入处罚;因此没有完成。

你还必须对书中的建议持保留态度——那个人写了数百万人使用的图书馆(那里有非常好的建议),但你也在做同样的事情吗?

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

然后,在某些情况下返回实际可变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元素。

最新更新