繁体   English   中英

为什么Java中的List.of()不返回类型化的不可变列表?

[英]Why does List.of() in Java not return a typed immutable list?

由java中的方法List.of(E... elements)返回的列表确实返回一个不可变列表,但通过查看创建的列表,这根本不可见。 创建的列表只会抛出一个Exception,而不是根本不显示更改列表的可能性。 我的观点是, List.of(E... elements)应该返回一个extends ListImmutableList 这样,用户可以决定他是否愿意表现出这种不变性的事实。 但我没有发现任何人抱怨或显示替代解决方案。 即使是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方法。

任何人都可以告诉我为什么没有人关心这个(看似根本)的问题?

我会说,因为通常集合倾向于(或者至少应该)被视为“默认不可变”(意味着你很少修改你没有创建的集合),所以指定“这是不可变的”并不是非常重要”。 如果你愿意,指定“你可以安全地修改这个集合”会更有用。

其次,您建议的方法不起作用。 你不能扩展List和隐藏方法,所以唯一的选择是让它返回一个不是 List的子类型的ImmutableList 这将使它无用,因为它需要一个新的ImmutableList接口,并且任何现有的代码都无法使用它。

那么这个优化设计呢? 不,不是真的,但对于不会改变的向后兼容性。

从所有Collection类型中删除addremove等,并创建子接口MutableCollectionMutableListMutableSet会使Collection接口的数量增加一倍,这是一个需要考虑的复杂性成本。 此外,Collections没有完全分为Mutable和Immutable: Arrays.asList支持set ,但不支持add

最终需要权衡在类型系统中捕获多少以及在运行时强制执行多少。 合理的人可能不同意在哪里划线。

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

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

但也可以说,不可变性非常重要,应该是特殊的,并且即使不支持所有其他特征,也可以在类型层次结构中得到支持。 很公平。

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

ImmutableList <:List <:Collection

(其中<:表示“是”的子类型。)

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

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

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

这种方法无法解决的问题是通过向上转换“清除”不变性。 许多现有API使用CollectionIterable类型的参数定义方法。 如果您要将ImmutableList传递给这样的方法,它将丢失指示列表是不可变的类型信息。 要从中受益,您必须在任何地方添加不可变的重载。 或者,您可以在任何地方添加instanceof检查。 两者都很混乱。

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

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

List <:ImmutableList

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

List <:ReadableList

哪个更准确。 但是, ReadableListImmutableList完全不同。

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

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

ulist的类型应该是什么? 这不是一成不变的,因为如果有人修改后台列表就会改变alist 现在考虑:

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

该类型应该是什么? 它肯定不是不可变的,因为它包含数组,并且数组总是可变的。 因此,说List.of返回不可变的东西是不合理的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM