如何将可变集合变成不可变集合 [英] How to turn a Mutable Collection into an Immutable one

查看:214
本文介绍了如何将可变集合变成不可变集合的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

我想将数据公开给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 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

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

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)

虽然上面的内容很难实现,但 Guava Eclipse集合.

I was writing a small piece of code in which I internally handle my data in a mutable map, which in turn has mutable lists.

I wanted to expose my data to the API user, but to avoid any unsafe publication of my data I wanted to expose it in immutable collections even when internally being handled by mutable ones.

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!
}

I managed to expose only Map and List (which are immutable) in the public API of my class, but the instances I am actually exposing are still inherently mutable.

Which means an API user could simply cast my returned map as an ImmutableMap and gain access to the precious private data internal to my class, which was intended to be protected of this kind of access.

I couldn't find a copy constructor in the collection factory methods mutableMapOf() or mutableListOf() and so I was wondering what is the best and most efficient way to turn a mutable collection into an immutable one.

Any advice or recommendations?

解决方案

Currently in Kotlin stdlib there are no implementations of List<T> (Map<K,V>) that would not also implement MutableList<T> (MutableMap<K,V>). However due to Kotlin's delegation feature the implementations become one liners:

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

You can also enhance the creation of the immutable counterparts with extension methods:

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)
    }
}

The above prevents a caller from modifying the List (Map) by casting to a different class. However there are still reasons to create a copy of the original container to prevent subtle issues like 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)

While the above is not hard to implement there are already implementations of immutable lists and maps in both Guava and Eclipse-Collections.

这篇关于如何将可变集合变成不可变集合的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆