Copia defensiva de una colección mutable en la class de datos Kotlin

Quiero tener una class de datos que acepte una list de solo lectura:

data class Notebook(val notes: List<String>) { } 

Pero también puede aceptar MutableList , porque es un subtipo de la List .

Por ejemplo, el siguiente código modifica la list pasada:

 fun main(args: Array<String>) { val notes = arrayListOf("One", "Two") val notebook = Notebook(notes) notes.add("Three") println(notebook) // prints: Notebook(notes=[One, Two, Three]) } 

¿Hay alguna forma de cómo realizar una copy defensiva de la list aprobada en la class de datos?

Creo que es mejor usar la biblioteca de JetBrains para collections inmutables: https://github.com/Kotlin/kotlinx.collections.immutable

Importar en tu proyecto

Agregue el repository bintray:

 repositories { maven { url "http://dl.bintray.com/kotlin/kotlinx" } } 

Agrega la dependencia:

 compile 'org.jetbrains.kotlinx:kotlinx-collections-immutable:0.1' 

Resultado:

 data class Notebook(val notes: ImmutableList<String>) {} fun main(args: Array<String>) { val notes = immutableListOf("One", "Two") val notebook = Notebook(notes) notes.add("Three") // creates a new collection println(notebook) // prints: Notebook(notes=[One, Two]) } 

Nota: También puede usar agregar y eliminar methods con ImmutableList, pero este método no modifica la list actual solo crea una nueva con sus cambios y la devuelve por usted.

Opción 1

Puede usar .toList() para hacer una copy, aunque necesitará otra propiedad interna que contenga la list de copy o necesita alejarse de una class de datos a una class normal.

Como una class de datos:

 data class Notebook(private val _notes: List<String>) { val notes: List<String> = _notes.toList() } 

El problema aquí es que su class de datos va a tener .equals() y .hashCode() basados ​​en una list potencialmente mutante.

Entonces la alternativa es usar una class normal:

 class Notebook(notes: List<String>) { val notes: List<String> = notes.toList() } 

opcion 2

El equipo de Kotlin también está trabajando en collections verdaderamente inmutables, es posible que pueda get una vista previa de ellas si son lo suficientemente estables para su uso: https://github.com/Kotlin/kotlinx.collections.immutable


Opción 3

Otra forma sería crear una interfaz que permita MutableList tipo descendiente de MutableList . Esto es exactamente lo que hace la biblioteca Klutter al crear una jerarquía de classs de delegado livianas que pueden envolver las lists para garantizar que no sea posible ninguna mutación. Como usan delegación, tienen poca carga. Puede usar esta biblioteca, o simplemente mirar el código fuente como un ejemplo de cómo crear este tipo de collections protegidas. Luego, cambie su método para solicitar esta versión protegida en lugar del original. Consulte el código fuente de Klutter ReadOnly Collection Wrappers y las testings asociadas para get ideas.

Como ejemplo del uso de estas classs de Klutter , la class de datos sería:

 data class Notebook(val notes: ReadOnlyList<String>) { 

Y la persona que llama se vería obligada a cumplir pasando una list envolvente que es bastante simple:

 val myList = mutableListOf("day", "night") Notebook(myList.toImmutable()) // copy and protect 

Lo que está sucediendo es que la persona que llama (al invocar asReadOnly() ) está haciendo la copy defensiva para satisfacer los requisitos de su método, y no hay forma de mutar la copy protegida debido a cómo están diseñadas estas classs.

Un defecto en la implementación de Klutter es que no tiene una jerarquía separada para ReadOnly vs. Immutable por lo que si la persona que llama llama asReadOnly() el titular de la list aún puede causar una mutación. Entonces, en su versión de este código (o una actualización de Klutter) sería mejor asegurarse de que todos sus methods de fábrica siempre hagan una copy y nunca permitan que estas classs se construyan de otra manera (es decir, que los constructores sean internal ). O tiene una segunda jerarquía que se usa cuando la copy se ha hecho claramente. La forma más sencilla es copyr el código en su propia biblioteca, eliminar los methods asReadOnly() dejando solo toImmutable() y hacer que todos los constructores de la class de colección sean internal .


Más información

ver también: Kotlin y collections inmutables?

No hay forma de anular la asignación de una propiedad declarada en el constructor primario. Si necesita una tarea personalizada, deberá moverla fuera del constructor, pero ya no podrá convertir su class en una class de datos.

 class Notebook(notes: List<String>) { val notes: List<String> = notes.toList() } 

Si debe mantener una class de datos, la única forma que veo de hacerlo es utilizar el bloque de init para hacer la copy, pero tendrá que hacer de su propiedad una var para poder hacer esto, ya que la primera asignación de la propiedad sucederá automáticamente, y aquí lo estás modificando después.

 data class Notebook(var notes: List<String>) { init { notes = notes.toList() } } 
  • Declaración de object global en kotlin
  • Kotlin - Sobrecarga del operador de invocación de la function
  • Estrategia JxBrowser para recuperar de manera eficiente el favicon
  • TextColor está cambiando a uno incorrecto
  • ¿Por qué no puedo especificar dónde quiero que se desplace mi file Kotlin en Android Studio?
  • ¿Cómo diseñar una class para json cuando uso Gson en Kotlin?
  • @CreationTimestamp y @UpdateTimestamp no funcionan en Kotlin
  • Solo la primera testing pasa con TestScheduler cuando se ejecutan varias testings (Kotlin)
  • Kotlin DialogFragment editText editable always null
  • Mapa de Mybatis COALESCE (int, 0) a java.lang.Long
  • Firebase Admin SDK no se puede conectar a la database desde el file jar