【问题标题】:How to turn a Mutable Collection into an Immutable one如何将可变集合变成不可变集合
【发布时间】:2016-06-20 20:55:26
【问题描述】:

我正在编写一小段代码,在其中我在一个可变映射中内部处理我的数据,而该映射又具有可变列表。

我想向 API 用户公开我的数据,但为了避免任何不安全的数据发布,我想在不可变集合中公开它,即使在内部由可变集合处理时也是如此。

class School {

    val roster: MutableMap<Int, MutableList<String>> = mutableMapOf<Int, MutableList<String>>()

    fun add(name: String, grade: Int): Unit {
        val students = roster.getOrPut(grade) { mutableListOf() }
        if (!students.contains(name)) {
            students.add(name)
        }
    }

    fun sort(): Map<Int, List<String>> {
        return db().mapValues { entry -> entry.value.sorted() }
                .toSortedMap()
    }

    fun grade(grade: Int) = db().getOrElse(grade, { listOf() })
    fun db(): Map<Int, List<String>> = roster //Uh oh!
}

我设法在我的类的公共 API 中仅公开了 MapList(它们是不可变的),但我实际公开的实例仍然本质上是可变的。

这意味着 API 用户可以简单地将我返回的地图转换为 ImmutableMap 并获得对我的类内部的宝贵私有数据的访问权,而这些数据旨在受到这种访问的保护。

我在集合工厂方法 mutableMapOf()mutableListOf() 中找不到复制构造函数,所以我想知道将可变集合转换为不可变集合的最佳和最有效的方法是什么。

有什么建议或建议吗?

【问题讨论】:

    标签: kotlin


    【解决方案1】:

    使用 CollectionsMutable 列表转换为 Immutable 列表,示例:

    可变列表:

    val mutableList = mutableListOf<String>()
    

    转换为不可变列表:

    val immutableList = Collections.unmodifiableList(mutableList)
    

    【讨论】:

      【解决方案2】:

      目前在 Kotlin 标准库中没有 List&lt;T&gt; (Map&lt;K,V&gt;) 的实现不会也实现 MutableList&lt;T&gt; (MutableMap&lt;K,V&gt;)。但是由于Kotlin's delegation feature,实现变成了一个内衬:

      class ImmutableList<T>(private val inner:List<T>) : List<T> by inner
      class ImmutableMap<K, V>(private val inner: Map<K, V>) : Map<K, V> by inner
      

      您还可以使用扩展方法增强不可变副本的创建:

      fun <K, V> Map<K, V>.toImmutableMap(): Map<K, V> {
          if (this is ImmutableMap<K, V>) {
              return this
          } else {
              return ImmutableMap(this)
          }
      }
      
      fun <T> List<T>.toImmutableList(): List<T> {
          if (this is ImmutableList<T>) {
              return this
          } else {
              return ImmutableList(this)
          }
      }
      

      以上内容可防止调用者通过转换为不同的类来修改List (Map)。但是,仍然有理由创建原始容器的副本以防止出现 ConcurrentModificationException 等微妙问题:

      class ImmutableList<T> private constructor(private val inner: List<T>) : List<T> by inner {
          companion object {
              fun <T> create(inner: List<T>) = if (inner is ImmutableList<T>) {
                      inner
                  } else {
                      ImmutableList(inner.toList())
                  }
          }
      }
      
      class ImmutableMap<K, V> private constructor(private val inner: Map<K, V>) : Map<K, V> by inner {
          companion object {
              fun <K, V> create(inner: Map<K, V>) = if (inner is ImmutableMap<K, V>) {
                  inner
              } else {
                  ImmutableMap(hashMapOf(*inner.toList().toTypedArray()))
              }
          }
      }
      
      fun <K, V> Map<K, V>.toImmutableMap(): Map<K, V> = ImmutableMap.create(this)
      fun <T> List<T>.toImmutableList(): List<T> = ImmutableList.create(this)
      

      虽然上面的实现并不难,但在GuavaEclipse-Collections 中已经实现了不可变列表和映射。

      【讨论】:

      • 一个更完整的版本被添加到 Klutter 中,阻止所有相关操作,例如从列表中收到的 Iterator、从列表中收到的 subList、从地图中收到的 entrySet 等等。见:stackoverflow.com/a/38002121/3679676
      【解决方案3】:

      herehere 所述,您需要为此编写自己的List 实现,或使用现有的实现(想到番石榴的ImmutableList,或Eclipse Collections 建议Andrew) .

      Kotlin 仅通过接口强制执行列表 (im) 可变性。没有不实现MutableListList 实现。

      即使是惯用的 listOf(1,2,3) 最终也会调用 Kotlin 的 ArraysUtilJVM.asList(),后者调用 Java 的 Arrays.asList(),后者返回一个普通的旧 Java ArrayList

      如果您更关心保护自己的内部列表,而不是不变性本身,您当然可以复制整个集合并将其作为List 返回,就像 Kotlin 所做的那样:

      return ArrayList(original)
      

      【讨论】:

      • 小修正,Arrays.asList 返回java.util.Arrays.ArrayList(不要与java.util.ArrayList 混淆)。这个列表在某种程度上是不可变的,因为您不能添加或删除项目,但您可以通过索引替换项目。
      【解决方案4】:

      只需在您的MutableMap 上拨打toMap()

      val myMap = mutableMapOf<String, String>("x" to "y").toMap()
      

      完成。

      这同样适用于列表。

      【讨论】:

        【解决方案5】:

        我知道这是一个 Kotlin 特定的问题,@Malt 是正确的,但我想添加一个替代方案。特别是我发现Eclipse Collections,正式的 GS-Collections,在大多数情况下作为 Guava 的更好替代品,它很好地补充了 Kotlin 的内置集合。

        【讨论】:

          【解决方案6】:

          一个经典的解决方案是复制你的数据,这样即使修改了,也不会影响私有类的属性:

          class School {
              private val roster = mutableMapOf<Int, MutableList<String>>()
          
              fun db(): Map<Int, List<String>> = roster.mapValuestTo {it.value.toList}
          }
          

          【讨论】:

            【解决方案7】:

            如果您需要将MutableMap&lt;String, Any&gt; 严格转换为ImmutableMap&lt;String, Any&gt;,这是行不通的

            val iMap : ImmutableMap<String, Any> = mutMap.toMap() // error
            

            我找到了唯一可行的方法:

            val iMap : ImmutableMap<String, Any> =  ImmutableMap.builder<String, Any>().putAll(mutMap).build()
            

            【讨论】:

              猜你喜欢
              • 2012-01-19
              • 2011-03-11
              • 1970-01-01
              • 2012-02-12
              • 1970-01-01
              • 2011-12-04
              • 1970-01-01
              • 2021-01-12
              • 2011-11-24
              相关资源
              最近更新 更多