首页 文章

Kotlin和Immutable Collections?

提问于
浏览
31

我正在学习Kotlin,看起来我可能希望在明年使用它作为我的主要语言 . 但是,我一直在研究Kotlin是否存在不可变的集合,而我正试图弄清楚是否需要使用Google Guava .

有人可以给我一些指导吗?它默认使用不可变集合吗?哪些运算符返回可变或不可变的集合?如果没有,是否有计划实施它们?

5 回答

  • 2

    来自标准库的Kotlin的List只读:

    interface List<out E> : Collection<E> (source)
    

    通用有序的元素集合 . 此接口中的方法仅支持对列表的只读访问;通过MutableList接口支持读/写访问 . 参数E - 列表中包含的元素类型 .

    如上所述,还有MutableList

    interface MutableList<E> : List<E>, MutableCollection<E> (source)
    

    支持添加和删除元素的通用有序元素集合 . 参数E - 列表中包含的元素类型 .

    因此,Kotlin通过其接口强制执行只读行为,而不是像默认Java实现那样在运行时抛出异常 .

    同样,有一个 MutableCollectionMutableIterableMutableIteratorMutableListIteratorMutableMapMutableSet ,请参阅stdlib文档 .

  • 25

    令人困惑但有三种,而不是两种不可变性:

    • Mutable - 你应该改变收藏品(Kotlin的 MutableList

    • Readonly - 你不应该改变它(Kotlin的 List ),但有些东西可能(强制转换为Mutable,或者从Java改变)

    • 永恒 - 没有人可以改变它( Guava 的不可变集合)

    因此,在情况(2) List 只是一个没有变异方法的接口,但是如果将它转换为 MutableList ,则可以更改实例 .

    使用Guava(案例(3)),即使使用演员或其他线程,您也可以安全地更改集合 .

    为了直接使用Java集合,Kotlin选择了readonly,因此使用Java集合没有任何开销或转换 .

  • 14

    正如您在其他答案中看到的那样,Kotlin具有可变集合的只读接口,可让您通过只读镜头查看集合 . 但是可以通过转换或从Java操纵来绕过该集合 . 但是在合作的Kotlin代码中很好,大多数用法都不需要真正的不可变集合,如果你的团队避免使用可变形式的集合,那么你可能不需要完全不可变的集合 .

    Kotlin集合允许复制变化突变以及惰性突变 . 因此,要回答部分问题, filtermapflatmap ,operator + - 等所有内容都会在用于非惰性集合时创建副本 . 当在 Sequence 上使用时,他们会在访问时将值修改为集合,并继续保持惰性(导致另一个 Sequence ) . 虽然对于 Sequence ,调用诸如 toListtoSettoMap 之类的任何内容都将导致最终副本生成 . 通过命名约定,几乎任何以 to 开头的东西都在复制 .

    换句话说,大多数操作符都会返回与您开始时相同的类型,如果该类型是“只读”,那么您将收到一个副本 . 如果该类型是惰性的,那么您将懒惰地应用更改,直到您完全要求收集 .

    有些人出于其他原因需要它们,例如并行处理 . 在这些情况下,最好是查看专为此目的而设计的高性能集合 . 并且只在这些情况下使用它们,而不是在所有一般情况下 .

    在JVM世界中,很难避免与需要标准Java集合的库互操作,并且转换到这些集合或从这些集合转换会给不支持公共接口的库增加许多痛苦和开销 . Kotlin提供良好的互操作和缺乏转换,并通过 Contract 进行只读保护 .

    因此,如果您无法避免需要不可变集合,Kotlin可以轻松处理JVM空间中的任何内容:

    此外,Kotlin团队正在为Kotlin本地开发Immutable Collections,可以在这里看到:https://github.com/Kotlin/kotlinx.collections.immutable

    还有许多其他的收集框架可以满足所有不同的需求和限制,Google是您寻找它们的朋友 . Kotlin团队没有理由需要为其标准库重新发明它们 . 你有很多选择,他们专注于不同的事情,如性能,内存使用,非拳击,不变性等."Choice is Good" ...因此其他一些:HPCCHPCC-RTFastUtilKolobokeTrove以及......

    甚至还有类似Pure4J的努力,因为Kotlin现在支持Annotation处理,也许可以为Kotlin提供类似理想的端口 .

  • 14

    Kotlin 1.0在标准库中不会有不可变的集合 . 但它确实具有只读和可变接口 . 没有什么能阻止你使用第三方不可变集合库 .

    Kotlin的List接口"support only read-only access to the list"中的方法,而MutableList接口中的方法支持"adding and removing elements" . 然而,这两者都只是 interfaces .

    Kotlin的 List 接口在编译时强制执行只读访问,而不是将此类检查推迟到运行时,如java.util.Collections.unmodifiableList(java.util.List)(“返回指定列表的不可修改视图... [where]尝试修改返回的列表...结果在UnsupportedOperationException . “_ 965660

    考虑以下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)
    }
    

    请注意如何 readOnlyListList 和方法,如 add 无法解析(并不会编译), mutableList 可以自然突变,并 addimmutableList (从谷歌 Guava ),也可以在编译时间内解决,但抛出一个异常,在运行 .

    所有上述断言都通过了最后一个断言,导致 Exception in thread "main" java.lang.AssertionError: Expected <[1, 2, 3, 4]>, actual <[1, 2, 3]>. ,即我们成功地改变了只读 List

    请注意,使用 listOf(...) 而不是 arrayListOf(...) 会返回一个有效的不可变列表,因为您无法将其强制转换为任何可变列表类型 . 但是,对变量使用 List 接口不会阻止 MutableList 分配给它( MutableList<E> extends List<E> ) .

    最后,请注意Kotlin(以及Java)中的接口不能强制实现不变性"cannot store state"(参见Interfaces) . 因此,如果您想要一个不可变的集合,您需要使用类似Google Guava提供的集合 .


    另见ImmutableCollectionsExplained · google/guava Wiki · GitHub

  • 7

    NOTE: 这个答案就在这里,因为代码很简单,而且是开源的,您可以使用这个想法使您创建的集合不可变 . 它不仅仅是作为图书馆的广告 .

    Klutter library中,新的Kotlin Immutable包装器使用Kotlin委托来包装现有的Kotlin集合界面,保护层没有任何性能损失 . 然后,没有办法转换集合,它的迭代器或其他可能返回到可以修改的集合 . 它们变得无效 .

    Klutter 1.20.0发布,它为现有集合添加了不可变保护器,基于@miensol的SO答案提供了一个轻量级的集合代表,可防止任何修改途径,包括转换为可变类型然后修改 . 而且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 <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

相关问题