【问题标题】:Why does List.of() in Java not return a typed immutable list?为什么 Java 中的 List.of() 不返回类型化的不可变列表?
【发布时间】:2020-01-15 04:33:08
【问题描述】:

java 中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();

但即使是这个类也有一个(已弃用的)addremove 方法。

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

【问题讨论】:

  • 好的 .. 为什么你认为应该这样做?为什么你认为如果某件事没有按照你想象的方式工作,那它是错的还是坏的?
  • 好吧,List.of(...) 是一个标准的 JDK 类,而 ImmutableList 是像 Guava 这样的第三方库的一部分。 List.of(...) 为什么要返回这样的列表?
  • 扩展 List 的不可变集合都违反了 Liskov 替换原则——它们声明的返回类型不会改变这一点,它一开始就被破坏了,并且没有真正的方法来修复它(不改变什么 @ 987654331@其实是)
  • "(尽管有很多代码)" 好消息,你不需要所有的代码,ImmutableList.copyOf(list)ImmutableList.of("one", "two", "three") 也一样。

标签: java collections guava apache-commons


【解决方案1】:

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

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

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

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

ImmutableList <: list collection>

(其中&lt;: 表示“是”的子类型。)

这当然可以做到,但是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 的子接口,我们希望它成为超级接口:

列表 <:>

这样,ImmutableList 不必指定所有这些 mutator 方法抛出异常,它们根本不会出现在接口中。这很好,只是这个模型完全错误。由于ArrayListList,这意味着ArrayList 也是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 返回不可变的东西是合理的,这一点完全不清楚。

【讨论】:

  • 这是一个决定性的答案,你可以从做出决定的实际人之一那里得到“为什么”:) 当然,你可以不同意这些原因,但是这些是原因。
  • 如果我们这样做了,为什么这些不可变集合仍然没有优化的forEachspliterator 实现(JDK14-ea)?令人尴尬的是,最新、最优化空间的集合类型对流处理的支持最差。我们可以像 list.spliterator().hasCharacteristics(Spliterator.IMMUTABLE) 这样直接检查,而无需引入新的 API,NONULL 也是如此,但它不起作用。
【解决方案2】:

从所有 Collection 类型中删除 addremove 等并创建子接口 MutableCollectionMutableListMutableSet 将使 Collection 接口的数量增加一倍,这是需要考虑的复杂性成本。此外,集合并没有明确地分为可变和不可变:Arrays.asList 支持set,但不支持add

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

【讨论】:

    【解决方案3】:

    我想说,由于通常集合倾向于(或至少应该)被视为“默认情况下不可变”(这意味着您很少修改不是您创建的集合),因此指定“这是不可变的”。指定“如果您愿意,您可以安全地修改此集合”会更有用。

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

    那么这是最优设计吗?不,不是真的,但为了向后兼容,这不会改变。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-11-26
      • 1970-01-01
      • 1970-01-01
      • 2015-06-26
      • 1970-01-01
      • 1970-01-01
      • 2018-03-02
      • 2011-03-17
      相关资源
      最近更新 更多