如何将Mutable Collection变成一个不可变的集合

Edw*_*rzo 34 kotlin

我正在编写一小段代码,我在内部处理可变映射中的数据,而后者又有可变列表.

我想将我的数据暴露给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!
}
Run Code Online (Sandbox Code Playgroud)

我设法只在我的类的公共API中公开Map和List(它是不可变的),但我实际暴露的实例仍然具有固有的可变性.

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

我找不到在收集工厂方法一个拷贝构造函数mutableMapOf()或mutableListOf(),所以我想知道什么是将可变集合到一个不变的一个最好的和最有效的方式.

有什么建议或建议吗?

Ben*_*nny 18

使用集合将可变列表转换为不可变列表,示例:

可变列表:

val mutableList = mutableListOf<String>()
Run Code Online (Sandbox Code Playgroud)

转换为不可变列表:

val immutableList = Collections.unmodifiableList(mutableList)
Run Code Online (Sandbox Code Playgroud)


mie*_*sol 16

目前在Kotlin stdlib中没有List<T>(Map<K,V>)的实现也不会实现 MutableList<T>(MutableMap<K,V>).但是由于Kotlin的委托功能,实现成为一个衬里:

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
Run Code Online (Sandbox Code Playgroud)

您还可以使用扩展方法增强不可变对应项的创建:

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

以上内容可防止调用者通过强制转换为不同的类来修改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)
Run Code Online (Sandbox Code Playgroud)

虽然上面的内容并不难实现,但是在Guava和Eclipse-Collections中已经有了不可变列表和映射的实现.


Mal*_*alt 8

如前所述这里和这里,你需要编写自己的List这种执行,或使用现有的一个(番石榴的ImmutableList想到,或者Eclipse的集合作为安德鲁建议).

Kotlin仅通过接口强制执行list(im)可变性.没有List实现也没有实现MutableList.

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

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

return ArrayList(original)
Run Code Online (Sandbox Code Playgroud)

  • 小修正,`Arrays.asList`返回一个`java.util.Arrays.ArrayList`(不要与`java.util.ArrayList`混淆).此列表有些不可变,因为您无法添加或删除项目,但您可以按索引替换项目. (3认同)

Ren*_*ann 7

只需调用toMap()您的MutableMap.

val myMap = mutableMapOf<String, String>("x" to "y").toMap()
Run Code Online (Sandbox Code Playgroud)

完毕。

这同样适用于列表。

  • 也许这在某些时候是正确的,但看看实现,这仍然会导致可变映射:`else -&gt; toMutableMap()` (4认同)