为什么 Kotlin 有集合的可变版本?

Why does Kotlin have Mutable versions of collections?

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

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

嗯....好吧...其实,我明白 val 与对象的 'mutability' 没有任何关系,而是 're-initializability'对象的。

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

来自 Kotlin 文档:

The read-only collection types are covariant. This means that, if a Rectangle class inherits from Shape, you can use a List<Rectangle> anywhere the List<Shape> is required. In other words, the collection types have the same subtyping relationship as the element types. Maps are covariant on the value type, but not on the key type.

In turn, mutable collections aren't covariant; otherwise, this would lead to runtime failures. If MutableList<Rectangle> was a subtype of MutableList<Shape>, you could insert other Shape inheritors (for example, Circle) into it, thus violating its Rectangle type argument.

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

TL;DR

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

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

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

The read-only collection types are covariant...the collection types have the same subtyping relationship as the element types.

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

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

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

// This is valid!
stackShapes(rectangleList)

可变集合,另一方面,可以从 读取和 写入。因此,没有 sub-typing 或 super-typing 是可能的。来自 Kotlin 文档:

...mutable collections aren't covariant; otherwise, this would lead to runtime failures. If MutableList<Rectangle> was a subtype of MutableList<Shape>, you could insert other Shape inheritors (for example, Circle) into it, thus violating its Rectangle type argument.

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 可以将 type-checks 添加到可变集合的所有 write-methods 中......然后您可以让它们协变, 你不需要单独的不可变集合!"

这是对的,只是它完全违背了 Kotlin 的核心哲学;尽可能避免 nulls 和运行时错误。您会看到,只要 type-check 失败,此类 Collection 的方法就必须 return null - 或者引发异常。这只会在运行时变得明显,因为这可以通过简单地使可变集合不变来避免……这正是 Kotlin 所做的。