我正在学习Kotlin,并且很可能在未来一年内将其用作主要语言。然而,我一直在得到相互矛盾的研究结果,即Kotlin是否具有不可变集合,我正在努力弄清楚是否需要使用Google Guava。
请问是否有人能给我一些指导?它是否默认使用不可变集合?哪些操作符返回可变或不可变集合?如果没有,是否有计划实现它们?
我正在学习Kotlin,并且很可能在未来一年内将其用作主要语言。然而,我一直在得到相互矛盾的研究结果,即Kotlin是否具有不可变集合,我正在努力弄清楚是否需要使用Google Guava。
请问是否有人能给我一些指导?它是否默认使用不可变集合?哪些操作符返回可变或不可变集合?如果没有,是否有计划实现它们?
Kotlin标准库中的List
是只读的:
interface List<out E> : Collection<E> (source)
这是一个通用的有序元素集合。此接口中的方法仅支持对列表的只读访问;通过
MutableList
接口支持读写访问。参数
E - 列表中包含的元素类型。
如上所述,还有 MutableList
接口。
interface MutableList<E> : List<E>, MutableCollection<E> (source)
支持添加和删除元素的一般有序元素集合。
参数
E - 列表中包含的元素类型。
因此,Kotlin通过其接口强制执行只读行为,而不像默认Java实现在运行时抛出异常。
同样,还有MutableCollection
、MutableIterable
、MutableIterator
、MutableListIterator
、MutableMap
和MutableSet
等,详见stdlib文档。
虽然有点混淆,但实际上有三种不可变性而非两种:
MutableList
)List
),但某些东西可能会(转换为Mutable或从Java更改)因此,在情况(2)中,List
只是一个接口,它没有突变的方法,但您可以通过将其转换为MutableList
来更改实例。
使用Guava(case (3)),您可以避免其他人甚至通过线程更改集合。
Kotlin选择只读是为了直接使用Java集合,因此在使用Java集合时没有额外的开销或转换。
List
只是只读的,而不是不可变的。其他调用者(例如Java)可以更改列表。 Kotlin调用者可能会转换列表并更改它。 没有不可变的保护。 - Jayson Minardfilter
,map
,flatmap
,运算符+
-
这样的东西当针对非懒惰收集使用时会创建副本。 当在Sequence
上使用时,它们会在访问集合时修改值并继续保持懒惰(导致另一个Sequence
)。 尽管对于Sequence
,调用任何像toList
,toSet
,toMap
这样的东西都会导致最终副本被制作。 根据命名约定,几乎以to
开头的任何内容都在制作副本。甚至有一些类似Pure4J的努力,由于Kotlin现在支持注解处理,可能可以将其移植到Kotlin以实现类似的理念。
Kotlin 1.0标准库中没有不可变集合,但是有只读和可变的接口。同时,您可以使用第三方不可变集合库。
Kotlin的List
接口中的方法“仅支持对列表进行只读访问”,而其MutableList
接口中的方法则支持“添加和删除元素”。但是,这两者都只是接口。
Kotlin的List
接口在编译时强制执行只读访问,而不是像java.util.Collections.unmodifiableList(java.util.List)
那样将此类检查推迟到运行时(它“返回指定列表的不可修改视图……[其中]试图修改返回的列表……会导致一个UnsupportedOperationException
。” 它并不强制实施不可变性。
请考虑以下Kotlin代码:
import com.google.common.collect.ImmutableList
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
fun main(args: Array<String>) {
val readOnlyList: List<Int> = arrayListOf(1, 2, 3)
val mutableList: MutableList<Int> = readOnlyList as MutableList<Int>
val immutableList: ImmutableList<Int> = ImmutableList.copyOf(readOnlyList)
assertEquals(readOnlyList, mutableList)
assertEquals(mutableList, immutableList)
// readOnlyList.add(4) // Kotlin: Unresolved reference: add
mutableList.add(4)
assertFailsWith(UnsupportedOperationException::class) { immutableList.add(4) }
assertEquals(readOnlyList, mutableList)
assertEquals(mutableList, immutableList)
}
readOnlyList
是一个 List
,而诸如 add
的方法无法解析(也无法编译),mutableList
可以自然地被改变,而 immutableList
(来自 Google Guava)上的 add
也可以在编译时解析,但会在运行时引发异常。Exception in thread "main" java.lang.AssertionError: Expected <[1, 2, 3, 4]>, actual <[1, 2, 3]>
。即我们成功地修改了只读 List
!listOf(...)
而不是 arrayListOf(...)
将返回一个有效的不可变列表,因为您无法将其转换为任何可变列表类型。但是,对于变量使用 List
接口并不会防止将 MutableList<E>
分配给它(MutableList<E>
扩展了 List<E>
)。1.20.0
的更新,新增了针对现有集合的不可变保护器。该保护器基于SO答案@miensol提供的轻量级代理来防止任何修改途径,包括将其强制转换为可变类型然后进行修改。而且,Klutter还进一步保护子集合(如迭代器、listIterator、entrySet等)。所有这些操作都被禁止,使用Kotlin委托实现大多数方法,性能无损耗。只需调用myCollection.asReadonly()
(保护)或myCollection.toImmutable()
(复制再保护),结果是相同的受保护接口。
下面是代码示例,展示了这种技术简单易行的方式,基本上通过将接口委托给实际类,同时重写突变方法和任何返回的子集合都被即时包装。/**
* Wraps a List with a lightweight delegating class that prevents casting back to mutable type
*/
open class ReadOnlyList <T>(protected val delegate: List<T>) : List<T> by delegate, ReadOnly, Serializable {
companion object {
@JvmField val serialVersionUID = 1L
}
override fun iterator(): Iterator<T> {
return delegate.iterator().asReadOnly()
}
override fun listIterator(): ListIterator<T> {
return delegate.listIterator().asReadOnly()
}
override fun listIterator(index: Int): ListIterator<T> {
return delegate.listIterator(index).asReadOnly()
}
override fun subList(fromIndex: Int, toIndex: Int): List<T> {
return delegate.subList(fromIndex, toIndex).asReadOnly()
}
override fun toString(): String {
return "ReadOnly: ${super.toString()}"
}
override fun equals(other: Any?): Boolean {
return delegate.equals(other)
}
override fun hashCode(): Int {
return delegate.hashCode()
}
}
/**
* Wraps the List with a lightweight delegating class that prevents casting back to mutable type,
* specializing for the case of the RandomAccess marker interface being retained if it was there originally
*/
fun <T> List<T>.asReadOnly(): List<T> {
return this.whenNotAlreadyReadOnly {
when (it) {
is RandomAccess -> ReadOnlyRandomAccessList(it)
else -> ReadOnlyList(it)
}
}
}
/**
* Copies the List and then wraps with a lightweight delegating class that prevents casting back to mutable type,
* specializing for the case of the RandomAccess marker interface being retained if it was there originally
*/
@Suppress("UNCHECKED_CAST")
fun <T> List<T>.toImmutable(): List<T> {
val copy = when (this) {
is RandomAccess -> ArrayList<T>(this)
else -> this.toList()
}
return when (copy) {
is RandomAccess -> ReadOnlyRandomAccessList(copy)
else -> ReadOnlyList(copy)
}
}
而且,测试显示了一些以前允许修改的技巧,但现在不再允许,并阻止使用这些包装器进行强制转换和调用。
fun Iterable<T>.toImmutableList(): ImmutableList<T>
fun Iterable<T>.toImmutableSet(): ImmutableSet<T>
fun Iterable<T>.toPersistentList(): PersistentList<T>
fun Iterable<T>.toPersistentSet(): PersistentSet<T>