Как превратить Mutable Collection в неизменяемую

Я писал небольшой фрагмент кода, в котором я внутренне обрабатываю свои данные на изменяемой карте, которая, в свою очередь, имеет изменяемые списки.

Я хотел разоблачить свои данные пользователю 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! } 

Мне удалось отобразить только открытый и сопоставленный Map и List (открытый) в общедоступном API моего класса, но экземпляры, которые я фактически просматриваю, по- прежнему по своей сути изменяемы.

Это означает, что пользователь API может просто передать мою возвращенную карту как ImmutableMap и получить доступ к ценным личным данным, внутренним для моего класса, который должен был быть защищен от такого доступа.

Я не мог найти конструктор копий в методах фабрики коллекции mutableMapOf() или mutableListOf() и поэтому мне было интересно, какой лучший и эффективный способ превратить изменчивую коллекцию в неизменную.

Любые советы или рекомендации?

В настоящее время в Kotlin stdlib нет реализаций List<T> ( Map<K,V> ), которые также не будут реализовывать MutableList<T> ( MutableMap<K,V> ). Однако из-за принадлежности делегации Котлина, реализации становятся одним лайнером:

 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-Collections уже реализованы неизменные списки и карты.

Как уже упоминалось здесь и здесь , вам нужно будет написать свою собственную реализацию List для этого или использовать существующую (неподходящий List Guava на ум или Eclipse Collections, как предложил Эндрю ).

Kotlin применяет изменчивость списка (im) только по интерфейсу. Нет реализаций List которые также не реализуют MutableList .

Даже идиоматический listOf(1,2,3) заканчивается вызовом ArraysUtilJVM.asList() Kotlin, который вызывает Java Arrays.asList() который возвращает простой старый Java ArrayList .

Если вы больше заботитесь о защите собственного внутреннего списка, чем о самой неизменяемости, вы можете, конечно, скопировать всю коллекцию и вернуть ее в виде List , как это делает Котлин:

 return ArrayList(original) 

Я знаю, что это конкретный вопрос Котлина, а @Malt верен, но я хотел бы добавить альтернативу. В частности, я нахожу коллекцию Eclipse Collections , формально GS-Collections, в качестве лучшей альтернативы Guava для большинства случаев и хорошо дополняет собранные коллекции Kotlin.

Классическим решением является копирование ваших данных, так что даже если они будут изменены, изменение не повлияет на свойство private class:

 class School { private val roster = mutableMapOf<Int, MutableList<String>>() fun db(): Map<Int, List<String>> = roster.mapValuestTo {it.value.toList} } 
Intereting Posts
Почему существует разница между сборщиками coroutine для CompletableFuture и ListenableFuture? Создание списка из ResultSet Исключение NullPointerException при попытке доступа к унаследованному полю Связывание данных: ObservableField со значением лямбда не компилируется Как сделать многосвязывание с помощью контейнера поиска зависимостей Kodein? JNI: чтение и запись в прямом буфере eclipse организует импорт, не работающий с файлами kotlin Есть ли какая-нибудь библиотека для работы с монадами на котлин? Как получить вход от пользователя в Android Studio в числовом значении и преобразовать его в переменную int в kotlin? Ошибка компиляции команды с закрытыми классами KMango: обновить коллекцию с помощью col.updateOne / col.updateMany Котлин: Путаница с лямбдами и дженериками Как написать пользовательский getter, если свойства класса определены в конструкторе? Как преобразовать цифру Char (0-9) в числовое значение? Переключение контекста kotlin coroutine при тестировании Android-презентатора