Kotlin和不可变的collections?
我正在学习Kotlin,看起来很可能我想在明年内把它当作我的主要语言。 然而,我一直在收到相互矛盾的研究,Kotlin是否有不可变的集合,我试图弄清楚是否需要使用Google Guava。
有人可以给我一些指导呢? 默认情况下使用Immutable集合吗? 什么操作符返回可变或不可变集合? 如果没有,是否有计划实施它们?
标准库中的Kotlin List
是只读的:
interface List : Collection (source)
元素的通用有序集合。 这个接口中的方法只支持对列表的只读访问。 通过MutableList接口支持读/写访问。
参数
E – 列表中包含的元素的types。
如前所述,还有MutableList
interface MutableList : List , MutableCollection (source)
支持添加和删除元素的通用有序元素集合。
参数
E – 列表中包含的元素的types。
因此,Kotlin通过其接口强制执行只读行为,而不像像默认的Java实现那样在运行时抛出exception。
同样,还有一个MutableCollection
, MutableIterable
, MutableIterator
, MutableListIterator
, MutableMap
和MutableSet
,请参阅stdlib文档。
正如您在其他答案中所看到的,Kotlin只读接口可以让您通过只读镜头查看集合。 但是这个集合可以通过转换或Java处理来绕过。 但是在合作的Kotlin代码中,很好,大多数的用法并不需要真正的不可变的集合,如果你的团队避免把集合转换为集合的可变forms,那么也许你不需要完全不可变的集合。
Kotlin系列允许同时发生拷贝突变和懒惰突变。 所以要回答你的问题的一部分,像filter
, map
, flatmap
,运营商+
-
所有创建副本对非懒惰集合使用时。 在Sequence
上使用时,它们将修改值作为访问时的集合,并继续为惰性(导致产生另一个Sequence
)。 虽然对于一个Sequence
,调用toList
, toSet
, toMap
等任何东西都会导致最终的拷贝。 通过命名约定几乎任何开始与复制。
换句话说,大多数操作符会返回与您开始时相同的types,如果该types是“只读”,则会收到副本。 如果这种types是懒惰的,那么你将懒洋洋地应用这个改变,直到你要求完整的收集。
有些人希望他们出于其他原因,如并行处理。 在这种情况下,最好看一下为此目的设计的高性能系列。 只在这些情况下使用它们,而不是在一般情况下。
在JVM世界中,很难避免与需要标准Java集合的库进行交互,并且对这些集合的转换会给不支持通用接口的库增加许多痛苦和开销。 Kotlin提供了良好的互操作性和缺乏转换性,只有合同保护。
所以,如果你不能避免想要不可变的集合,Kotlin可以轻松处理JVM空间中的任何东西:
- 番石榴 ( https://github.com/google/guava )
- 使用Kotlin助手( https://github.com/andrewoma/dexx/blob/master/kollection/README.md)Dexx将Scala集合中的一个端口移植到Java( https://github.com/andrewoma/dexx )
- Eclipse Collections (以前称为GS-Collections)是一个非常高性能,兼容JDK的并行处理性能最高的处理器,具有不可变和可变的变化(主页: https://www.eclipse.org/collections/和Github: https:// github。 com / eclipse / eclipse-collections )
- PCollections ( http://pcollections.org/ )
此外,Kotlin团队正在为Kotlin本地开发不可变集合,可以在这里看到这个function: https : //github.com/Kotlin/kotlinx.collections.immutable
还有许多其他的收集框架可以满足所有不同的需求和限制,Google是您find它们的朋友。 Kotlin团队没有必要为标准库重新创建它们。 你有很多的选择,他们专注于不同的事情,如性能,记忆使用,不拳击,不变性等“选择是好的”…因此,一些其他: HPCC , HPCC – RT , FastUtil , Koloboke , 特罗夫和更多…
甚至有像Pure4J这样的努力,既然Kotlin现在支持Annotation处理,也许可以有类似理想的端口给Kotlin。
这是令人困惑的,但有三种,而不是两种不变性:
- 可变 – 你应该改变集合(Kotlin的
MutableList
) - 只读 – 你不应该改变它(Kotlin的
List
),但可能会(转换为可变,或从Java改变) - 不变的 – 没有人能改变它(番石榴的不变的collections)
所以万一(2) List
只是一个接口,没有变异的方法,但你可以改变实例,如果你把它转换为MutableList
。
使用番石榴(案例(3)),你可以安全的从任何人改变收集,即使是一个铸造或从另一个线程。
Kotlin选择只读,以便直接使用Java集合,所以在使用Java集合时没有开销或转换。
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) { val readOnlyList: List = arrayListOf(1, 2, 3) val mutableList: MutableList = readOnlyList as MutableList val immutableList: ImmutableList = 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),但会抛出exception运行。
除了最后一个导致Exception in thread "main" java.lang.AssertionError: Expected <[1, 2, 3, 4]>, actual <[1, 2, 3]>.
即我们成功突变了只读List
!
请注意,使用listOf(...)
而不是arrayListOf(...)
将返回一个有效的不可变列表,因为您无法将其转换为任何可变列表types。 但是,为variables使用List
接口并不会阻止将MutableList
分配给它( MutableList
extends List
)。
最后,请注意,Kotlin(以及Java)中的接口不能强制不变性,因为它“无法存储状态”(请参阅接口 )。 因此,如果你想要一个不可变的集合,你需要使用类似Google Guava提供的东西。
请参阅ImmutableCollectionsExplained·google / guava Wiki·GitHub
注: 这个答案在这里,因为代码是简单的和开放源代码,你可以使用这个想法使你的集合,你创建不可变的。 它不只是作为图书馆的广告。
在Klutter图书馆 ,新的Kotlin不可变的包装,使用Kotlin代表团包裹现有的Kotlin收集界面与保护层没有任何性能影响。 那么就没有办法将集合,它的迭代器或其他集合转换成可以修改的东西。 他们变得不可变。
Klutter
1.20.0
发布,它为现有集合添加了不可变的保护器,基于@miensol的SO回答,提供了一个围绕集合的轻量级代理 ,可以防止任何修改,包括将其转换为可变types,然后进行修改。 Klutter通过保护子集合(例如iterator,listIterator,entrySet等)更进一步。所有这些门都关闭了,并且使用Kotlin委托给大多数在性能上没有打击的方法。 只需调用myCollection.asReadonly()
( protect )或myCollection.toImmutable()
( 复制然后保护 ),结果是相同的接口但保护。
下面是代码示例,通过基本上将接口委托给实际的类,同时覆盖突变方法以及返回的任何子集合被动态地包装起来。
/** * Wraps a List with a lightweight delegating class that prevents casting back to mutable type */ open class ReadOnlyList (protected val delegate: List ) : List by delegate, ReadOnly, Serializable { companion object { @JvmField val serialVersionUID = 1L } override fun iterator(): Iterator { return delegate.iterator().asReadOnly() } override fun listIterator(): ListIterator { return delegate.listIterator().asReadOnly() } override fun listIterator(index: Int): ListIterator { return delegate.listIterator(index).asReadOnly() } override fun subList(fromIndex: Int, toIndex: Int): List { 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() } }
除了助手扩展function,使访问更容易:
/** * 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 List .asReadOnly(): List { 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 List .toImmutable(): List { val copy = when (this) { is RandomAccess -> ArrayList (this) else -> this.toList() } return when (copy) { is RandomAccess -> ReadOnlyRandomAccessList(copy) else -> ReadOnlyList(copy) } }
你可以看到这个想法,并推断从这个代码中创建缺失的类,重复其他引用types的模式。 或者在这里查看完整的代码:
而且测试显示了一些允许修改之前的技巧,但现在不能,以及使用这些包装器的阻塞转换和调用。