Kotlin 1.0在标准库中不会有不可变的集合。但它确实具有只读和可变性 接口 。没有什么能阻止你使用第三方不可变集合库。
Kotlin的方法 List 接口“仅支持对列表的只读访问”,而其中的方法 MutableList 界面支持“添加和删除元素”。然而,这两者都只是 的 接口 强> 。
List
MutableList
科特林的 List 接口在编译时强制执行只读访问,而不是将此类检查推迟到运行时 java.util.Collections.unmodifiableList(java.util.List) (“返回指定列表的不可修改的视图... [where]尝试修改返回的列表...导致a UnsupportedOperationException “。 的 它不会强制执行不变性。 强>
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 可以自然地变异,和 add 上 immutableList (来自Google Guava)也可以在编译时解决,但在运行时抛出异常。
readOnlyList
add
mutableList
immutableList
除了导致的最后一个断言之外,所有上述断言都会通过 Exception in thread "main" java.lang.AssertionError: Expected <[1, 2, 3, 4]>, actual <[1, 2, 3]>. 即我们成功地改变了只读 List !
Exception in thread "main" java.lang.AssertionError: Expected <[1, 2, 3, 4]>, actual <[1, 2, 3]>.
注意使用 listOf(...) 代替 arrayListOf(...) 返回一个有效的不可变列表,因为您无法将其强制转换为任何可变列表类型。但是,使用 List 变量的接口不会阻止a MutableList 被分配给它( MutableList<E> 扩展 List<E> )。
listOf(...)
arrayListOf(...)
MutableList<E>
List<E>
最后,请注意Kotlin(以及Java)中的接口无法强制实现不变性,因为它“无法存储状态”(请参阅 接口 )。因此,如果您想要一个不可变的集合,您需要使用类似Google Guava提供的集合。
也可以看看 ImmutableCollections解释了google / guava的维基文章GitHub
正如您在其他答案中看到的那样,Kotlin具有可读集合的只读接口,可让您通过只读镜头查看集合。但是可以通过转换或从Java操纵来绕过该集合。但是在合作的Kotlin代码中很好,大多数用法都不需要真正的不可变集合,如果你的团队避免使用集合的可变形式,那么你可能不需要完全不可变的集合。
Kotlin集合允许复制变更突变以及惰性突变。所以要回答你的部分问题,比如 filter , map , flatmap ,运营商 + - 所有在用于非惰性集合时都会创建副本。当用于 Sequence 他们将值修改为访问时的集合,并继续保持懒惰(导致另一个 Sequence )。虽然是为了 Sequence ,打电话给任何人 toList , toSet , toMap 将导致最终复制。通过命名约定几乎任何开头的东西 to 正在制作副本。
filter
map
flatmap
+
-
Sequence
toList
toSet
toMap
to
换句话说,大多数操作员返回与您开始时相同的类型,如果该类型是“只读”,那么您将收到一份副本。如果该类型是懒惰的,那么您将懒惰地应用更改,直到您完整地请求集合。
有些人出于其他原因需要它们,例如并行处理。在这些情况下,最好是查看专为此目的而设计的高性能集合。并且只在这些情况下使用它们,而不是在所有一般情况下。
在JVM世界中,很难避免与需要标准Java集合的库互操作,并且转换到这些集合或从这些集合转换会给不支持公共接口的库增加许多痛苦和开销。 Kotlin提供了良好的互操作性和缺乏转换,并通过合同进行了只读保护。
因此,如果您无法避免需要不可变集合,Kotlin可以轻松处理JVM空间中的任何内容:
此外,Kotlin团队正在为Kotlin本地开发Immutable Collections,可以在这里看到: https://github.com/Kotlin/kotlinx.collections.immutable
还有许多其他的收集框架可以满足所有不同的需求和限制,Google是您寻找它们的朋友。 Kotlin团队没有理由需要为其标准库重新发明它们。你有很多选择,他们专注于不同的事情,如性能,内存使用,非拳击,不变性等。“选择是好的”...因此其他一些: HPCC , HPCC-RT , FastUtil , Koloboke , 特罗韦 和更多...
甚至还有像Pure4J那样的努力,因为Kotlin现在支持Annotation处理,也许可以为Kotlin提供类似理想的端口。
的 注意: 强> 这个答案就在这里,因为代码很简单,而且是开源的,您可以使用这个想法来创建您创建的集合是不可变的。它不仅仅是作为图书馆的广告。
在 Klutter图书馆 ,是新的Kotlin Immutable包装器,使用Kotlin委托将现有的Kotlin集合界面包裹起来,保护层没有任何性能损失。然后,无法将集合,其迭代器或其他集合强制转换为可以修改的集合。它们变得无效。
Klutter 1.20.0 发布,它为现有的集合添加了不可变保护器,基于a 回答@miensol 提供围绕集合的轻量级委托,防止任何修改途径,包括转换为可变类型然后修改。而且Klutter更进一步保护了子集合,例如iterator,listIterator,entrySet等。所有这些门都被关闭,并且使用Kotlin委派对大多数方法都没有影响性能。只需致电 myCollection.asReadonly() ( 保护 ) 要么 myCollection.toImmutable() ( 复制然后保护 )结果是相同的接口但受保护。
1.20.0
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) } }
您可以看到这个想法,并通过此代码创建缺少的类,重复其他引用类型的模式。或者在这里查看完整代码:
https://github.com/kohesive/klutter/blob/master/core-jdk6/src/main/kotlin/uy/klutter/core/common/Immutable.kt
并且测试显示了一些允许修改的技巧,但现在没有,以及使用这些包装器的阻塞转换和调用。
https://github.com/kohesive/klutter/blob/master/core-jdk6/src/test/kotlin/uy/klutter/core/collections/TestImmutable.kt