【问题标题】:Why does Kotlin have Mutable versions of collections?为什么 Kotlin 有可变版本的集合?
【发布时间】:2020-11-30 10:25:20
【问题描述】:

我有一个关于 Kotlin 集合的一般性问题。

当我们有 valvar 的区别时,为什么会有这么多集合的可变版本(例如 MutableList)?

嗯....好吧...实际上,我知道val 与对象的“可变性”无关,而是与对象的“可重新初始化”有关。

但是这就提出了一个问题....为什么MutableList 不是默认值?

【问题讨论】:

  • 你读过documentation吗?原因之一是不可变列表是协变的,而可变列表不是。
  • 可变集合允许您修改其中的内容。 var 允许您为变量分配不同的集合。可变性可用于帮助优化代码,因为它可以避免在您仅更改部分内容时重复复制列表的全部内容。或者您可能需要它进行一些递归操作。仅举几个例子。
  • @Pawel 为什么不创建一个协变的统一接口,但在add()remove() 等所有修改方法中都强制执行类型检查?所以,如果你尝试非法修改,你会得到null
  • 泛型提供编译时安全性,并且(至少在 JVM 上)可变/不可变变体均由本机集合实现支持。运行时检查(这会带来相当多的开销)是违反设计的,只是提出问题。

标签: kotlin language-design mutablelist


【解决方案1】:

来自 Kotlin 文档:

只读集合类型是协变的。这意味着,如果 Rectangle 类继承自 Shape,您可以在任何需要 List<Shape> 的地方使用 List<Rectangle>。换句话说,集合类型与元素类型具有相同的子类型关系。映射在值类型上是协变的,但在键类型上不是。

反过来,可变集合不是协变的;否则,这将导致运行时失败。如果 MutableList<Rectangle>MutableList<Shape> 的子类型,您可以将其他 Shape 继承者(例如 Circle)插入其中,从而违反其 Rectangle 类型参数。

换句话说,如果它是不可变的,那么您就知道所有类型都是相同的。如果没有,您可能有不同的继承者。

【讨论】:

  • 为什么不创建一个统一的接口,它是协变的,但在add()remove() 等所有修改方法中都强制执行类型检查?所以,如果你尝试非法修改,你会得到null
  • 我没有编写规范,但我猜这是权衡问题?每次添加或删除时,您都会添加类型检查。
【解决方案2】:

TL;DR

单独地,可变和不可变集合能够公开在单个界面中无法共存的有用功能:

  1. 可变集合可以从读取和写入。但 Kotlin 力求避免所有运行时故障,因此,这些可变集合是不变的。
  2. 不可变集合是协变的,但它们是……嗯……不可变的。尽管如此,Kotlin 确实提供了使用这些不可变集合做有用事情的机制(例如过滤值或从现有的不可变集合创建新的不可变集合)。您可以通过the long list of convenience functions for Kotlin's (immutable) List interface 获取示例。

Kotlin 中的不可变集合不能添加或删除元素;它们只能从中读取。但是这种明显的限制使得对不可变集合进行一些子类型化成为可能。来自 Kotlin 文档:

只读集合类型是协变的...集合类型与元素类型具有相同的子类型关系。

这意味着,如果 Rectangle 类是 Shape 类的子类,您可以在需要时将 List<Rectangle> 对象放入 List<Shape> 变量中:

fun stackShapes(val shapesList: List<Shape>) {
    ...
}

val rectangleList = listOf<Rectangle>(...)

// This is valid!
stackShapes(rectangleList)

另一方面,可变集合可以从读取和写入。正因为如此,它们不可能有子类型或超类型。来自 Kotlin 文档:

...可变集合不是协变的;否则,这将导致运行时失败。如果 MutableList&lt;Rectangle&gt;MutableList&lt;Shape&gt; 的子类型,您可以将其他 Shape 继承者(例如 Circle)插入其中,从而违反其 Rectangle 类型参数。

val rectangleList = mutableListOf<Rectangle>(...);
val shapesList: MutableList<Shape> = rectangleList // MutableList<Rectangle>-type object in MutableList<Shape>-type variable

val circle = Circle(...)
val shape: Shape = circle // Circle-type object in Shape-type variable

// Runtime Error!
shapesList.add(shape) // You're actually trying to add a Circle to a MutableList<Rectangle>
// If rectanglesList couldn't be put into a variable with type MutableList<Shape> in the first place, you would never have run into this problem.

此时,您可能会想:“那又怎样?Kotlin 可以将类型检查添加到可变集合的所有写入方法......然后您可以允许它们是协变的,和 你不需要单独的不可变集合!”

这是真的,只是它完全违背了 Kotlin 的核心哲学;尽可能避免nulls 和运行时错误。您会看到,每当类型检查失败时,此类 Collection 的方法都必须返回 null - 或引发异常。这只会在运行时变得明显,因为这可以通过简单地使可变集合保持不变来避免......这正是 Kotlin 所做的。

【讨论】:

    猜你喜欢
    • 2019-02-03
    • 1970-01-01
    • 2017-01-19
    • 2016-02-17
    • 1970-01-01
    • 2017-03-21
    • 1970-01-01
    • 2011-01-12
    • 2011-12-23
    相关资源
    最近更新 更多