首页 > 解决方案 > 为什么 Java 中的 List.of() 不返回类型化的不可变列表?

问题描述

java中的方法List.of(E... elements)返回的列表确实返回了一个不可变的列表,但是通过查看创建的列表根本不可见。创建的列表只是抛出一个异常,而不是根本不显示更改列表的可能性。我的意思是,那List.of(E... elements)应该返回一个ImmutableListthat extends List。通过这种方式,用户可以决定他是否愿意展示这个不变性的事实。但我没有发现任何人抱怨或展示替代解决方案。甚至 Guava 和 Apache Commons 默认也不这样做。只有 Guava 提供了创建它的可能性(尽管有很多代码):

List<String> list = new ArrayList<String>(Arrays.asList("one", "two", "three"));
ImmutableList<String> unmodifiableList = ImmutableList.<String>builder().addAll(list).build();

但即使是这个类也有一个(不推荐使用的)addremove方法。

谁能告诉我为什么没有人关心这个(看似基本的)问题?

标签: javacollectionsguavaapache-commons

解决方案


并不是没有人关心;这是一个相当微妙的问题。

没有“不可变”集合接口系列的最初原因是担心接口扩散。可能存在不仅用于不变性的接口,还可能存在用于同步和运行时类型检查的集合,以及可以设置元素但不能添加或删除的集合(例如Arrays.asList)或可以删除​​但不能添加元素的集合(例如,Map.keySet)。

但也可以说,不变性是如此重要,以至于它应该是特殊情况,并且即使不支持所有其他特征,类型层次结构也支持它。很公平。

最初的建议是ImmutableList扩展接口List,如

不可变列表 <: 列表 <: 集合

(其中<:的意思是“是”的子类型。)

这当然可以做到,但随后ImmutableList会继承 的所有方法List,包括所有 mutator 方法。必须对他们做点什么;子接口不能从超接口“取消继承”方法。可以做的最好的事情是指定这些方法抛出异常,提供这样做的默认实现,并可能将这些方法标记为已弃用,以便程序员在编译时收到警告。

这有效,但没有多大帮助。这种接口的实现根本不能保证是不可变的。恶意或错误的实现可能会覆盖 mutator 方法,或者它可以简单地添加更多改变状态的方法。任何使用的程序ImmutableList都不能假设该列表实际上是不可变的。

对此的一种变体是ImmutableList使用而不是接口,定义它的 mutator 方法来抛出异常,使它们成为最终的,并且不提供公共构造函数,以限制实现。事实上,这正是 Guava'sImmutableList所做的。如果您信任 Guava 开发人员(我认为他们很有名气),那么如果您有一个 GuavaImmutableList实例,您就可以确信它实际上是不可变的。例如,您可以将它存储在一个字段中,并且知道它不会意外地从您的下方改变。但这也意味着你不能添加另一个ImmutableList实现,至少在不修改 Guava 的情况下不能。

这种方法没有解决的一个问题是通过向上转换“擦洗”不变性。很多现有的 API 都定义了参数类型为Collection或的方法Iterable。如果您将 an 传递ImmutableList给这样的方法,它将丢失指示列表是不可变的类型信息。要从中受益,您必须在任何地方添加不可变风格的重载。或者,您可以在instanceof任何地方添加检查。两者都很乱。

(请注意,JDKList.copyOf回避了这个问题。即使没有不可变类型,它也会在复制之前检查实现,并避免不必要地复制。因​​此,调用者可以List.copyOf用来制作防御性副本而不受惩罚。)

作为替代方案,有人可能会争辩说我们不想ImmutableList成为 的子接口List,我们希望它成为超级接口:

列表 <: 不可变列表

这样,ImmutableList不必指定所有这些 mutator 方法都抛出异常,它们根本不会出现在接口中。这很好,只是这个模型完全错误。既然ArrayList是 a List,那意味着ArrayList也是 a ImmutableList,这显然是荒谬的。问题是“不可变”意味着对子类型的限制,这不能在继承层次结构中完成。相反,它需要重命名,以允许在向下层级添加功能时添加,例如,

列表 <: 可读列表

哪个更准确。但是,ReadableListImmutableList.

最后,还有一堆我们没有考虑过的语义问题。其中之一涉及不变性不可修改性。Java 具有支持不可修改性的 API,例如:

List<String> alist = new ArrayList<>(...);
??? ulist = Collections.unmodifiableList(alist);

类型应该ulist是什么?它不是一成不变的,因为如果有人更改了支持列表,它就会改变alist。现在考虑:

???<String[]> arlist = List.of(new String[] { ... }, new String[] { ... });

类型应该是什么?它当然不是不可变的,因为它包含数组,而且数组总是可变的。因此,说List.of返回不可变的东西是合理的,这一点还不清楚。


推荐阅读