提问人:Johannes Rabauer 提问时间:9/13/2019 最后编辑:Mark RotteveelJohannes Rabauer 更新时间:10/18/2023 访问量:10848
为什么 Java 中的 List.of() 不返回类型化的不可变列表?
Why does List.of() in Java not return a typed immutable list?
问:
Java 中方法返回的列表确实返回了一个不可变列表,但通过查看创建的列表,这根本不可见。创建的列表只是抛出一个异常,而不是根本不显示更改列表的可能性。
我的观点是,那应该返回一个 that .这样,用户可以决定他是否愿意展示这个不变的事实。List.of(E... elements)
List.of(E... elements)
ImmutableList
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();
但即使是这个类也有一个(已弃用的)和方法。add
remove
谁能告诉我为什么没有人关心这个(看似根本的)问题?
答:
我想说的是,由于通常集合倾向于(或至少应该)被视为“默认不可变”(这意味着您很少修改未创建的集合),因此指定“这是不可变的”并不是很重要。指定“如果您愿意,可以安全地修改此集合”会更有用。
其次,您建议的方法行不通。您无法扩展和隐藏方法,因此唯一的选择是使其返回不是 的子类型的 。这将使它变得毫无用处,因为它需要一个新的接口,并且任何现有代码都无法使用它。List
ImmutableList
List
ImmutableList
那么这是最佳设计吗?不,不是真的,但对于向后兼容性,这不会改变。
从所有 Collection 类型中删除 、 等并创建子接口 , 将使 Collection 接口的数量增加一倍,这是需要考虑的复杂性成本。此外,Collections 没有明确地分为 Mutable 和 Immutable:支持 ,但不支持 。add
remove
MutableCollection
MutableList
MutableSet
Arrays.asList
set
add
最终,需要权衡在类型系统中捕获多少,以及在运行时强制执行多少。理性的人可能会不同意在哪里划清界限。
这并不是说没有人在乎;这是一个相当微妙的问题。
没有“不可变”集合接口系列的最初原因是担心接口扩散。可能不仅有用于不可变性的接口,还可能有同步和运行时类型检查的集合,以及可以设置元素但不能添加或删除的集合(例如,Arrays.asList)或可以从中删除但不能添加元素的集合(例如,Map.keySet)。
但也可以说,不可变性是如此重要,以至于它应该是特例的,并且在类型层次结构中支持它,即使不支持所有这些其他特征。很公平。
最初的建议是让接口扩展为ImmutableList
List
ImmutableList <:列表<:集合
(其中表示“是”的子类型”。<:
这当然可以做到,但随后会继承 的所有方法,包括所有 mutator 方法。必须对他们做点什么;子接口不能从超级接口“继承”方法。最好的办法是指定这些方法引发异常,提供这样做的默认实现,并可能将这些方法标记为已弃用,以便程序员在编译时收到警告。ImmutableList
List
这有效,但没有多大帮助。这种接口的实现根本不能保证是不可变的。恶意或有缺陷的实现可能会覆盖 mutator 方法,或者它可能只是添加更多改变状态的方法。任何使用的程序都不能假设列表实际上是不可变的。ImmutableList
对此的变体是使其成为一个类而不是一个接口,定义其赋值器方法以抛出异常,使它们成为最终值,并且不提供公共构造函数,以限制实现。事实上,这正是 Guava 的 ImmutableList
所做的。如果你信任 Guava 开发人员(我认为他们很有信誉),那么如果你有一个 Guava 实例,你就可以放心,它实际上是不可变的。例如,您可以将其存储在一个字段中,并知道它不会意外地从您下面改变。但这也意味着你不能添加另一个实现,至少在不修改 Guava 的情况下不能。ImmutableList
ImmutableList
ImmutableList
这种方法无法解决的一个问题是通过向上转换来“清理”不可变性。许多现有的 API 使用类型或 的参数来定义方法。如果将 an 传递给这样的方法,它将丢失指示列表不可变的类型信息。为了从中受益,您必须在任何地方添加不可变风味的重载。或者,您可以在所有位置添加检查。两者都很乱。Collection
Iterable
ImmutableList
instanceof
(请注意,JDK 的 List.copyOf
回避了这个问题。即使没有不可变的类型,它也会在复制之前检查实现,并避免不必要地进行复制。因此,来电者可以用来制作防御性副本而不受惩罚。List.copyOf
作为替代方案,有人可能会争辩说,我们不想成为 的子接口,我们希望它成为一个超级接口:ImmutableList
List
列表 <:ImmutableList
这样,就不必指定所有这些赋值器方法都抛出异常,它们根本不会出现在接口中。这很好,只是这个模型是完全错误的。既然是 ,那意味着也是 ,这显然是荒谬的。问题在于,“不可变”意味着对子类型的限制,这在继承层次结构中是无法做到的。相反,需要重命名它,以允许在层次结构向下移动时添加功能,例如,ImmutableList
ArrayList
List
ArrayList
ImmutableList
列表<:ReadableList
哪个更准确。但是,与 .ReadableList
ImmutableList
最后,还有一堆我们没有考虑的语义问题。一个是关于不变性与不可修改性。Java 具有支持不可修改性的 API,例如:
List<String> alist = new ArrayList<>(...);
??? ulist = Collections.unmodifiableList(alist);
应该是什么类型?它不是一成不变的,因为如果有人更改后备列表,它就会改变。现在考虑:ulist
alist
???<String[]> arlist = List.of(new String[] { ... }, new String[] { ... });
类型应该是什么?它当然不是不可变的,因为它包含数组,而数组始终是可变的。因此,完全不清楚说返回不可变的东西是否合理。List.of
评论
forEach
spliterator
list.spliterator().hasCharacteristics(Spliterator.IMMUTABLE)
NONULL
评论
List.of(...)
ImmutableList
List.of(...)
List
ImmutableList.copyOf(list)
ImmutableList.of("one", "two", "three")