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

时间:2016-06-20 20:55:26

标签: 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!
}

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

这意味着API用户可以简单地将我返回的地图强制转换为ImmutableMap,并获取对我班级内部的宝贵私人数据的访问权限,该数据旨在受到此类访问的保护。

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

有任何建议或建议吗?

6 个答案:

答案 0 :(得分:13)

目前在Kotlin stdlib中没有List<T>Map<K,V>)的实现也不会实现 MutableList<T>MutableMap<K,V>)。但是由于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)
    }
}

以上内容可防止调用者通过强制转换为其他类来修改ListMap)。但是,仍有理由创建原始容器的副本以防止出现诸如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中实现了不可变列表和地图。

答案 1 :(得分:8)

如上所述herehere,您需要为此编写自己的List实现,或者使用现有的实现(想到Guava的ImmutableList,或{{3如Eclipse Collections建议的那样)。

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

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

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

return ArrayList(original)

答案 2 :(得分:3)

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

答案 3 :(得分:3)

只需在您的toMap()上致电MutableMap

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

完成。

列表同样如此。

答案 4 :(得分:2)

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

可变列表:

val mutableList = mutableListOf<String>()

转换为不可变列表:

val immutableList = Collections.unmodifiableList(mutableList)

答案 5 :(得分:1)

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

class School {
    private val roster = mutableMapOf<Int, MutableList<String>>()

    fun db(): Map<Int, List<String>> = roster.mapValuestTo {it.value.toList}
}