首页 > 解决方案 > 为什么 Kotlin 有可变版本的集合?

问题描述

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

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

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

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

标签: kotlinlanguage-designmutablelist

解决方案


TL;博士

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

  1. 可以读取写入可变集合。但是 Kotlin 努力避免所有运行时故障,因此,这些可变集合是不变的。
  2. 不可变集合是协变的,但它们是……嗯……不可变的。尽管如此,Kotlin 确实提供了使用这些不可变集合做有用事情的机制(例如过滤值或从现有的不可变集合创建新的不可变集合)。您可以浏览Kotlin(不可变)接口的一长串便利函数List作为示例。

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<Rectangle>是 的子类型MutableList<Shape>,则可以将其他 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 所做的。


推荐阅读