【问题标题】:Kotlin and Immutable Collections?Kotlin 和不可变集合?
【发布时间】:2016-02-17 02:17:39
【问题描述】:

我正在学习 Kotlin,看起来我可能想在明年将它用作我的主要语言。但是,我不断得到相互矛盾的研究,即 Kotlin 是否具有不可变集合,我试图弄清楚是否需要使用 Google Guava。

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

【问题讨论】:

标签: kotlin


【解决方案1】:

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

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

换句话说,大多数运算符会返回与您开始时相同的类型,如果该类型是“只读”,那么您将收到一份副本。如果该类型是惰性的,那么您将惰性地应用更改,直到您需要整个集合。

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

在 JVM 世界中,很难避免与需要标准 Java 集合的库进行互操作,并且与这些集合之间的转换为不支持通用接口的库增加了很多痛苦和开销。 Kotlin 提供了互操作性和缺乏转换的良好组合,并通过合同提供只读保护。

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

此外,Kotlin 团队正在为 Kotlin 本地开发不可变集合,您可以在此处看到这项工作: https://github.com/Kotlin/kotlinx.collections.immutable

还有许多其他收集框架可满足所有不同的需求和限制,Google 是您寻找它们的好朋友。 Kotlin 团队没有理由需要为其标准库重新发明它们。你有很多选择,他们专注于不同的事情,如性能、内存使用、非装箱、不变性等。“选择是好的”......因此其他一些:HPCCHPCC-RT、@987654330 @、KolobokeTrove 等等……

甚至有像 Pure4J 这样的努力,因为 Kotlin 现在支持 Annotation 处理,也许可以有一个 Kotlin 的端口来实现类似的理想。

【讨论】:

    【解决方案2】:

    注意: 这个答案在这里是因为代码简单且开源,您可以使用这个想法来使您创建的集合不可变。它不只是作为图书馆的广告。

    Klutter library 中,是新的 Kotlin 不可变包装器,它们使用 Kotlin 委托来包装现有的 Kotlin 集合接口和保护层,而不会影响性能。那么就没有办法将集合、它的迭代器或它可能返回的其他集合转换为可以修改的东西。它们实际上是不可变的。

    Klutter 1.20.0 发布,为现有集合添加不可变保护器,基于 SO answer by @miensol 提供围绕集合的轻量级委托,可防止任何修改途径,包括转换为可变类型然后修改。 Klutter 更进了一步,保护了迭代器、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

    【讨论】:

      【解决方案3】:

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

      Kotlin 的List 接口中的方法“仅支持对列表的只读访问”,而其MutableList 接口中的方法支持“添加和删除元素”。然而,这两者都只是接口

      Kotlin 的 List 接口在编译时强制执行只读访问,而不是像 java.util.Collections.unmodifiableList(java.util.List) 那样将此类检查推迟到运行时进行(“返回指定列表的不可修改视图... [where] 尝试修改返回的列表...导致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)
      }
      

      注意readOnlyListListadd 等方法无法解析(也不会编译),mutableList 可以自然变异,add on immutableList(来自 Google Guava) 也可以在编译时解析,但在运行时抛出异常。

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

      请注意,使用 listOf(...) 而不是 arrayListOf(...) 返回一个有效的不可变列表,因为您不能将其转换为任何可变列表类型。但是,将List 接口用于变量并不会阻止将MutableList 分配给它(MutableList&lt;E&gt; 扩展List&lt;E&gt;)。

      最后,请注意 Kotlin(以及 Java)中的接口不能强制不变性,因为它“不能存储状态”(参见 Interfaces)。因此,如果您想要一个不可变的集合,您需要使用类似于 Google Guava 提供的集合。


      另见ImmutableCollectionsExplained · google/guava Wiki · GitHub

      【讨论】:

      • 您的回答对于 Kotlin 1.0 来说是正确的,不可变集合不在 1.0 版本的桌面上。
      【解决方案4】:

      这很令人困惑,但不变性分为三种,而不是两种:

      1. 可变的 - 你应该改变集合(Kotlin 的MutableList
      2. 只读 - 您不应该更改它(Kotlin 的 List),但可以(转换为 Mutable,或从 Java 更改)
      3. 不可变 - 没有人可以改变它(Guavas 的不可变集合)

      所以如果 (2) List 只是一个没有变异方法的接口,但是如果你将它转换为 MutableList,你可以更改实例。

      使用 Guava(案例 (3)),任何人都可以安全地更改集合,即使是使用演员表或来自其他线程。

      Kotlin 选择只读是为了直接使用 Java 集合,因此使用 Java 集合没有开销或转换。

      【讨论】:

      • Kotlin List 是只读的,不是不可变的。其他调用者(例如 Java)可能会更改列表。 Kotlin 调用者可能会转换列表并更改它。没有不可变的保护。
      【解决方案5】:

      标准库中 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 文档。

      【讨论】:

      • 有趣的方法虽然确保互操作性是有意义的。我可能会坚持使用 Guava 集合,因为(我上次检查时)性能要好得多。
      • Kotlin 集合是只读的,而不是不可变的@nhaarman。我建议对您的答案进行修改。
      • 请注意,如果您使用 arrayListOf() 创建一个列表,则返回的列表是一个 ArrayList,它是可变的。如果你想要一个不可变的列表,你必须使用 listOf() 来代替。
      猜你喜欢
      • 1970-01-01
      • 2019-02-03
      • 1970-01-01
      • 1970-01-01
      • 2011-11-24
      • 1970-01-01
      • 2012-01-19
      • 2011-03-11
      • 1970-01-01
      相关资源
      最近更新 更多