java - 为什么 Java 中的 List.of() 不返回类型化的不可变列表?
问题描述
java中的方法List.of(E... elements)
返回的列表确实返回了一个不可变的列表,但是通过查看创建的列表根本不可见。创建的列表只是抛出一个异常,而不是根本不显示更改列表的可能性。我的意思是,那List.of(E... elements)
应该返回一个ImmutableList
that 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
方法。
谁能告诉我为什么没有人关心这个(看似基本的)问题?
解决方案
并不是没有人关心;这是一个相当微妙的问题。
没有“不可变”集合接口系列的最初原因是担心接口扩散。可能存在不仅用于不变性的接口,还可能存在用于同步和运行时类型检查的集合,以及可以设置元素但不能添加或删除的集合(例如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
,这显然是荒谬的。问题是“不可变”意味着对子类型的限制,这不能在继承层次结构中完成。相反,它需要重命名,以允许在向下层级添加功能时添加,例如,
列表 <: 可读列表
哪个更准确。但是,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
返回不可变的东西是合理的,这一点还不清楚。
推荐阅读
- laravel - 在 Laravel 6 中注册另一个表关系
- swift - 如何使用 React Native 作为原生应用程序的共享业务逻辑模块(Swift/Kotlin)
- c++ - C++ 未定义的引用链接器错误与继承的模板虚函数和复杂的用法
- vue.js - Vuex getter 不返回数据
- javascript - 第二次调用 useEffect() 时页面崩溃
- jhipster - “无法读取未定义的属性‘charCodeAt’”
- javascript - 在 safari 中嵌入 svg 无法在 safari 浏览器中调用 click 事件
- python - Apache Nifi ExecuteStreamCommand UnicodeEncodeError 中的 python 代码:不允许代理
- r - 闪亮的反应式过滤
- reactjs - 如何使用带有情感/样式/宏的媒体查询?