如何把一个可变集合变成一个不可变的集合

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

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

class School { val roster: MutableMap<Int, MutableList> = mutableMapOf<Int, MutableList>() 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> { return db().mapValues { entry -> entry.value.sorted() } .toSortedMap() } fun grade(grade: Int) = db().getOrElse(grade, { listOf() }) fun db(): Map<Int, List> = roster //Uh oh! } 

我设法只公开我的类的公共API中的MapList (这是不可变的),但我真正暴露的实例仍然是天生的可变的。

这意味着一个API用户可以简单地将我的返回地图作为一个ImmutableMap进行投射,并访问我的课程内部的宝贵的私人数据,这是为了保护这种访问。

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

任何建议或建议?

目前在Kotlin stdlib中没有ListMap )的实现,它不会实现 MutableListMutableMap )。 但是,由于Kotlin的授权function,这些实现变成了一个内容:

 class ImmutableList(private val inner:List) : List by inner class ImmutableMap(private val inner: Map) : Map by inner 

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

 fun  Map.toImmutableMap(): Map { if (this is ImmutableMap) { return this } else { return ImmutableMap(this) } } fun  List.toImmutableList(): List { if (this is ImmutableList) { return this } else { return ImmutableList(this) } } 

以上防止调用者通过转换到不同的类来修改ListMap )。 但是,仍然有理由创建原始容器的副本,以防止诸如ConcurrentModificationException之类的细微问题:

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

虽然上面的代码不难实现,但在Guava和Eclipse-Collections中已经有了不可变列表和映射的实现。

正如在这里和这里提到的,你需要编写自己的List实现,或者使用现有的实现(想到Guava的ImmutableList,或者Eclipse集合,就像Andrew建议的那样)。

Kotlin仅通过接口强制实现列表(im)可变性。 没有List实现也不实现MutableList

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

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

 return ArrayList(original) 

我知道这是一个Kotlin的具体问题和@Malt是正确的,但我想补充一个替代方案。 特别是在大多数情况下,我发现Eclipse Collections是 GS-Collections的一个更好的替代方案,它可以很好地补充Kotlin的内置集合。

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

 class School { private val roster = mutableMapOf>() fun db(): Map> = roster.mapValuestTo {it.value.toList} }