我正在学习Kotlin,看起来我可能希望在明年使用它作为我的主要语言。然而,我不断得到相互矛盾的研究,Kotlin有或没有不可变的集合,我想弄清楚我是否需要使用Google Guava。
有人可以给我一些指导吗?它默认使用不可变集合吗?什么操作符返回可变或不可变的集合?如果没有,是否有计划实施它们?
答案 0 :(得分:26)
来自标准库的Kotlin List
只读:
interface List<out E> : Collection<E> (source)
通用的有序元素集合。此界面中的方法 仅支持对列表的只读访问;读/写访问权限 通过MutableList接口支持。
<强>参数强>
E - 列表中包含的元素类型。
如上所述,还有MutableList
interface MutableList<E> : List<E>, MutableCollection<E> (source)
支持添加和的元素的通用有序集合 删除元素。
<强>参数强>
E - 列表中包含的元素类型。
由于这个原因,Kotlin通过其接口强制执行readonly行为,而不是像默认的Java实现那样在运行时抛出异常。
同样,有MutableCollection
,MutableIterable
,MutableIterator
,MutableListIterator
,MutableMap
和MutableSet
,请参阅{{3文档。
答案 1 :(得分:17)
令人困惑,但有三种,而不是两种不变性:
MutableList
)List
),但有些东西可能(转向Mutable,或从Java改变)因此,如果情况(2)List
只是一个没有变异方法的接口,但是如果将其转换为MutableList
,则可以更改该实例。
使用Guava(案例(3)),即使是演员或其他线程,您也可以安全地更改集合。
为了直接使用Java集合,Kotlin选择了readonly,因此使用Java集合没有任何开销或转换。
答案 2 :(得分:14)
正如您在其他答案中看到的那样,Kotlin具有可变集合的只读接口,可让您通过只读镜头查看集合。但是可以通过转换或从Java操纵来绕过该集合。但是在合作的Kotlin代码中很好,大多数用途都不需要真正的不可变集合,如果你的团队避免使用可变形式的集合,那么你可能不需要完全不可变的集合。
Kotlin集合允许复制变更突变以及懒惰突变。因此,要回答部分问题,filter
,map
,flatmap
,运算符+
-
等内容都会在用于非惰性集合时创建副本。在Sequence
上使用时,他们会在访问时将值修改为集合,并继续保持惰性(导致另一个Sequence
)。虽然对于Sequence
,调用toList
,toSet
,toMap
之类的内容将导致最终副本生成。通过命名约定,几乎所有以to
开头的内容都会复制。
换句话说,大多数操作符都会返回与您开始时相同的类型,如果该类型是&#34; readonly&#34;然后你会收到一份副本。如果该类型是惰性的,那么您将懒惰地应用更改,直到您完整地要求收集。
有些人出于其他原因需要它们,例如并行处理。在这些情况下,最好是查看专为此目的而设计的高性能集合。并且只在这些情况下使用它们,而不是在所有一般情况下。
在JVM世界中,很难避免与需要标准Java集合的库互操作,并且转换到这些集合或从这些集合转换会给不支持公共接口的库增加许多痛苦和开销。 Kotlin提供了互操作和缺乏转换的良好组合,并通过合同进行了只读保护。
因此,如果你不能避免想要不可变的集合,那么Kotlin很容易使用JVM领域的任何东西:
此外,Kotlin团队正在为Kotlin本地开发Immutable Collections,可以在这里看到: http://pcollections.org/
还有许多其他的收集框架可以满足所有不同的需求和限制,Google是您寻找它们的朋友。 Kotlin团队没有理由需要为其标准库重新发明它们。你有很多选择,他们专注于不同的事情,如表现,记忆使用,非拳击,不变性等。&#34;选择是好的&#34; ......因此其他一些人:https://github.com/Kotlin/kotlinx.collections.immutable,HPCC,HPCC-RT,FastUtil,Koloboke等......
甚至还有类似Pure4J的努力,因为Kotlin现在支持Annotation处理,也许可以为Kotlin提供类似理想的端口。
答案 3 :(得分:7)
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
自然会被突变,{add
1 {} immutableList
(来自Google Guava)也可以在编译时解析,但在运行时抛出异常。
所有上述断言都通过,但最后一个断言导致Exception in thread "main" java.lang.AssertionError: Expected <[1, 2, 3, 4]>, actual <[1, 2, 3]>.
,即我们成功地改变了只读List
!
请注意,使用listOf(...)
而不是arrayListOf(...)
会返回一个有效的不可变列表,因为您无法将其强制转换为任何可变列表类型。但是,对变量使用List
接口不会阻止MutableList
分配给它(MutableList<E>
扩展List<E>
)。
最后,请注意Kotlin(以及Java)中的接口无法强制实现不可变性,因为它“无法存储状态”(请参阅Interfaces)。因此,如果您想要一个不可变的集合,您需要使用类似Google Guava提供的集合。
另见ImmutableCollectionsExplained · google/guava Wiki · GitHub
答案 4 :(得分:2)
注意: 这个答案就在这里,因为代码很简单,而且是开源的,您可以使用这个想法使您创建的集合不可变。它不仅仅是作为图书馆的广告。
在Klutter library中,新的Kotlin Immutable包装器使用Kotlin委托将现有的Kotlin集合界面包裹起来,保护层没有任何性能损失。然后,无法将集合,其迭代器或其他集合强制转换为可以修改的集合。它们变得无效。
Klutter
1.20.0
发布了为现有集合添加不可变保护程序,基于SO answer by @miensol提供了一个围绕集合的轻量级委托,可防止任何修改途径,包括转换为可变类型然后修改。而且Klutter更进一步保护了子集合,例如iterator,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)
}
}
您可以看到这个想法并推断从此代码创建缺少的类,这些类重复其他引用类型的模式。或者在这里查看完整代码:
测试显示了之前允许修改的一些技巧,但现在没有,以及使用这些包装器阻止的强制转换和调用。