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

为什么Kotlin有可变版本的集合?,kotlin,language-design,mutablelist,Kotlin,Language Design,Mutablelist,我有一个关于Kotlin系列的一般性问题 当我们有valvsvar的区别时,为什么会有这么多集合的可变版本(比如MutableList) 嗯……好吧……事实上,我知道val与对象的“可变性”无关,而是与对象的“重新初始化”有关 但是,这就提出了一个问题……为什么可变列表不是默认值?来自Kotlin文档: 只读集合类型是协变的。这意味着,如果矩形类继承自Shape,则可以在需要列表的任何位置使用列表。换句话说,集合类型与元素类型具有相同的子类型关系。贴图在值类型上是协变的,但在键类型上不是 反过来

我有一个关于Kotlin系列的一般性问题

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

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

但是,这就提出了一个问题……为什么
可变列表不是默认值?

来自Kotlin文档:

只读集合类型是协变的。这意味着,如果矩形类继承自Shape,则可以在需要
列表的任何位置使用
列表
。换句话说,集合类型与元素类型具有相同的子类型关系。贴图在值类型上是协变的,但在键类型上不是

反过来,可变集合不是协变的;否则,这将导致运行时失败。如果
MutableList
MutableList
的子类型,则可以在其中插入其他形状继承器(例如,圆),从而违反其矩形类型参数

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

TL;博士 分别而言,可变和不可变集合能够公开不能在单个接口中共存的有用功能:

  • 可变集合可以读取和写入。但是Kotlin努力避免所有运行时故障,因此,这些可变集合是不变的
  • 不可变集合是协变的,但它们是……嗯……不可变的。尽管如此,Kotlin还是提供了使用这些不可变集合执行有用操作的机制(如筛选值或从现有值创建新的不可变集合)。你可以举出例子

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

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

    这意味着,如果
    矩形
    类是
    形状
    类的子类,则可以根据需要在
    列表
    变量中放置
    列表
    对象:

    fun stackShapes(val shapesList:List){
    ...
    }
    val rectangleList=listOf(…)
    //这是有效的!
    堆栈形状(矩形列表)
    
    另一方面,可变集合可以读取和写入。因此,它们不可能使用子类型或超级类型。从Kotlin文档中:

    …可变集合不是协变的;否则,这将导致运行时失败。如果
    MutableList
    MutableList
    的子类型,则可以在其中插入其他形状继承器(例如,圆),从而违反其矩形类型参数

    val rectangleList=mutableListOf(…);
    val shapesList:MutableList=矩形列表//MutableList类型变量中的MutableList类型对象
    瓦尔圆=圆(…)
    val shape:shape=圆//形状类型变量中的圆类型对象
    //运行时错误!
    shapesList.add(shape)//实际上您正在尝试将一个圆添加到可变列表中
    //如果不能首先将rectanglesList放入类型为MutableList的变量中,您将永远不会遇到此问题。
    
    此时,您可能会想:“那又怎样?Kotlin可以只向可变集合的所有写入方法添加类型检查……然后您可以允许它们是协变的,而不需要单独的不可变集合!”


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

    您通读了吗?原因之一是不可变列表是协变的,而可变列表不是。可变集合允许您修改其中的内容。
    var
    允许您为变量分配不同的集合。可变性可以用来帮助优化代码,因为它可以避免在仅更改部分内容时重复复制列表的全部内容。或者您可能需要它来执行一些递归操作。仅举几个例子。@Pawel为什么不创建一个统一的接口,它是协变的,但在所有修改方法中都强制执行类型检查,如
    add()
    remove()
    ?因此,如果尝试非法修改,您将得到
    null
    。泛型提供编译时安全性,并且(至少在JVM上)这两个可变/不可变变量都有本机集合实现的支持。进行运行时检查(这会带来相当大的开销)是违背设计的,只是询问问题。为什么不制作一个统一的界面,它是协变的,但在所有修改方法中都强制执行类型检查,如
    add()
    remove()
    ?因此,如果您尝试非法修改,您将得到
    null
    。我没有编写规范,但我猜这是一个权衡的问题?每次添加或删除时都会添加类型检查。