Kotlin y collections inmutables?

Estoy aprendiendo Kotlin y parece probable que pueda querer usarlo como mi idioma principal en el próximo año. Sin embargo, sigo recibiendo investigaciones contradictorias de que Kotlin tiene o no collections inmutables y estoy tratando de averiguar si necesito usar Google Guava.

¿Puede alguien darme alguna orientación sobre esto? ¿Utiliza de forma pnetworkingeterminada collections inmutables? ¿Qué operadores devuelven collections mutables o inmutables? Si no, ¿hay planes para implementarlos?

La List de Kotlin de la biblioteca estándar es de solo lectura:

 interface List<out E> : Collection<E> (source) 

Una colección orderada genérica de elementos. Los methods en esta interfaz solo admiten acceso de solo lectura a la list; el acceso de lectura / escritura es compatible a través de la interfaz MutableList.

Parámetros
E – el tipo de elementos contenidos en la list.

Como se mencionó, también está MutableList

 interface MutableList<E> : List<E>, MutableCollection<E> (source) 

Una colección orderada genérica de elementos que admite agregar y eliminar elementos.

Parámetros
E – el tipo de elementos contenidos en la list.

Debido a esto, Kotlin impone el comportamiento de solo lectura a través de sus interfaces, en lugar de lanzar Excepciones en el time de ejecución, como lo hacen las implementaciones pnetworkingeterminadas de Java.

Del mismo modo, hay MutableCollection , MutableIterable , MutableIterator , MutableListIterator , MutableMap y MutableSet , consulte la documentation de stdlib .

Como puede ver en otras respuestas, Kotlin tiene interfaces de sólo lectura para collections mutables que le permiten ver una colección a través de una lente de solo lectura. Pero la colección se puede pasar por alto mediante conversión o manipulada desde Java. Pero en el código cooperativo de Kotlin está bien, la mayoría de los usos no necesitan collections verdaderamente inmutables y si su equipo evita los moldes a la forma mutable de la colección, entonces tal vez no necesite collections completamente inmutables.

Las collections de Kotlin permiten tanto mutaciones de copy en el cambio como mutaciones perezosas. Entonces, para responder a una parte de sus preguntas, cosas como filter , map , map flatmap , operadores + - crean copys cuando se usan contra collections no perezosas. Cuando se usan en una Sequence , modifican los valores como la colección a medida que se accede y continúan siendo flojos (lo que da como resultado otra Sequence ). Aunque para una Sequence , al invocar cualquier elemento como toList , toSet , toMap generará la copy final. Al nombrar a la convención, casi cualquier cosa que comience con es hacer una copy.

En otras palabras, la mayoría de los operadores le devuelven el mismo tipo que comenzó, y si ese tipo es "de solo lectura", recibirá una copy. Si ese tipo es flojo, entonces aplicará el cambio de forma perezosa hasta que exija la recolección en su totalidad.

Algunas personas los quieren por otros motivos, como el parallel processing. En esos casos, podría ser mejor search collections de alto performance diseñadas para esos fines. Y solo utilícelos en esos casos, no en todos los casos generales.

En el mundo de JVM, es difícil evitar la interoperabilidad con bibliotecas que quieren collections de Java estándar, y la conversión a / desde estas collections agrega mucho dolor y sobrecarga para las bibliotecas que no admiten las interfaces comunes. Kotlin ofrece una buena combinación de interoperabilidad y falta de conversión, con protección de solo lectura por contrato.

Entonces, si no puedes evitar querer collections inmutables, Kotlin trabaja fácilmente con cualquier cosa desde el espacio JVM:

Además, el equipo de Kotlin está trabajando en Colecciones Inmutables para Kotlin, ese esfuerzo se puede ver aquí: https://github.com/Kotlin/kotlinx.collections.immutable

Existen muchos otros frameworks de recostackción para todas las diferentes necesidades y limitaciones, Google es su amigo para encontrarlos. No hay ninguna razón por la cual el equipo de Kotlin necesite redeviselos para su biblioteca estándar. Tiene muchas opciones, y se especializan en diferentes cosas como performance, uso de memory, no boxeo, inmutabilidad, etc. "La opción es buena" … por lo tanto, algunas otras: HPCC , HPCC-RT , FastUtil , Koloboke , Trove y más …

Incluso hay esfuerzos como Pure4J que, como Kotlin ahora admite el procesamiento de annotations, puede tener un puerto para Kotlin para ideales similares.

Es confuso pero hay tres, no dos types de inmutabilidad:

  1. Mutable: se supone que debes cambiar la colección ( MutableList de Kotlin)
  2. Solo lectura: NO debes cambiarlo ( List de Kotlin) pero algo puede (enviarse a Mutable, o cambiar desde Java)
  3. Inmutable: nadie puede cambiarlo (collections inmutables de Guavas)

Entonces, en el caso (2), List es solo una interfaz que no tiene methods de mutación, pero puedes cambiar la instancia si la MutableList a MutableList .

Con Guava (caso (3)) está a salvo de cualquier persona para cambiar la colección, incluso con un elenco o de otro hilo.

Kotlin eligió ser de solo lectura para poder usar las collections de Java directamente, por lo que no hay gastos generales o conversiones en el uso de collections Java.

Kotlin 1.0 no tendrá collections inmutables en la biblioteca estándar. Sin embargo, tiene interfaces de solo lectura y mutables. Y nada le impide utilizar bibliotecas de collections inmutables de terceros.

Los methods en la interfaz de la List de Kotlin "admiten solo el acceso de solo lectura a la list", mientras que los methods en su interfaz MutableList admiten "agregar y eliminar elementos". Ambos, sin embargo, son solo interfaces .

La interfaz de la List de Kotlin impone el acceso de solo lectura en time de compilation en lugar de diferir tales comprobaciones al time de ejecución como java.util.Collections.unmodifiableList(java.util.List) (que "devuelve una vista no modificable de la list especificada … [donde] intenta modificar la list devuelta … da como resultado una UnsupportedOperationException . " No impone la inmutabilidad.

Considere el siguiente código de Kotlin:

 import com.google.common.collect.ImmutableList import kotlin.test.assertEquals import kotlin.test.assertFailsWith fun main(args: Array<String>) { val readOnlyList: List<Int> = arrayListOf(1, 2, 3) val mutableList: MutableList<Int> = readOnlyList as MutableList<Int> val immutableList: ImmutableList<Int> = ImmutableList.copyOf(readOnlyList) assertEquals(readOnlyList, mutableList) assertEquals(mutableList, immutableList) // readOnlyList.add(4) // Kotlin: Unresolved reference: add mutableList.add(4) assertFailsWith(UnsupportedOperationException::class) { immutableList.add(4) } assertEquals(readOnlyList, mutableList) assertEquals(mutableList, immutableList) } 

Observe cómo readOnlyList es una List y methods como add no se pueden resolver (y no se comstackn), mutableList puede mutableList naturalmente y add immutableList (de Google Guava) también se puede resolver en time de compilation, pero arroja una exception en time de ejecución

Todas las aserciones anteriores pasan con exception de la última que da como resultado Exception in thread "main" java.lang.AssertionError: Expected <[1, 2, 3, 4]>, actual <[1, 2, 3]>. es decir, ¡hemos mutado con éxito una List solo lectura!

Tenga en count que el uso de listOf(...) lugar de arrayListOf(...) devuelve una list efectivamente inmutable, ya que no puede convertirla a ningún tipo de list mutable. Sin embargo, el uso de la interfaz de List para una variable no impide que se le MutableList una MutableList ( MutableList<E> extends List<E> ).

Finalmente, tenga en count que una interfaz en Kotlin (así como en Java) no puede imponer la inmutabilidad ya que "no puede almacenar el estado" (consulte Interfaces ). Por lo tanto, si desea una colección inmutable, debe usar algo como los que proporciona Google Guava.


Ver también ImmutableCollectionsExplained · google / guava Wiki · GitHub

NOTA: Esta respuesta está aquí porque el código es simple y de código abierto y puede usar esta idea para hacer que sus collections que cree sean inmutables. No pretende ser solo un anuncio de la biblioteca.

En la biblioteca de Klutter , están las nuevas envolturas Inmutables de Kotlin que usan la delegación de Kotlin para envolver una interfaz de colección Kotlin existente con una capa protectora sin ningún impacto en el performance. Entonces no hay forma de convertir la colección, su iterador u otras collections, podría volver a ser algo que podría modificarse. Se vuelven en efecto Inmutables.

Se lanzó Klutter 1.20.0 que agrega protectores inmutables para collections existentes, basado en una respuesta SO por @miensol proporciona un delegado liviano alnetworkingedor de las collections que evita cualquier vía de modificación, incluida la conversión a un tipo mutable y luego la modificación. Y Klutter va un paso más allá al proteger subcollections como iterator, listIterator, entrySet, etc. Todas esas puertas están cerradas y, usando la delegación de Kotlin para la mayoría de los methods, no tiene impacto en el performance. Simplemente llame myCollection.asReadonly() ( protect ) o myCollection.toImmutable() ( copie y luego proteja ) y el resultado es la misma interfaz pero protegida.

Aquí hay un ejemplo del código que muestra cuán simple es la técnica, básicamente delegando la interfaz a la class real mientras se anulan los methods de mutación y cualquier subcolección devuelta se envuelve sobre la marcha.

 /** * Wraps a List with a lightweight delegating class that prevents casting back to mutable type */ open class ReadOnlyList <T>(protected val delegate: List<T>) : List<T> by delegate, ReadOnly, Serializable { companion object { @JvmField val serialVersionUID = 1L } override fun iterator(): Iterator<T> { return delegate.iterator().asReadOnly() } override fun listIterator(): ListIterator<T> { return delegate.listIterator().asReadOnly() } override fun listIterator(index: Int): ListIterator<T> { return delegate.listIterator(index).asReadOnly() } override fun subList(fromIndex: Int, toIndex: Int): List<T> { return delegate.subList(fromIndex, toIndex).asReadOnly() } override fun toString(): String { return "ReadOnly: ${super.toString()}" } override fun equals(other: Any?): Boolean { return delegate.equals(other) } override fun hashCode(): Int { return delegate.hashCode() } } 

Junto con las funciones de extensión auxiliar para facilitar el acceso:

 /** * Wraps the List with a lightweight delegating class that prevents casting back to mutable type, * specializing for the case of the RandomAccess marker interface being retained if it was there originally */ fun <T> List<T>.asReadOnly(): List<T> { return this.whenNotAlreadyReadOnly { when (it) { is RandomAccess -> ReadOnlyRandomAccessList(it) else -> ReadOnlyList(it) } } } /** * Copies the List and then wraps with a lightweight delegating class that prevents casting back to mutable type, * specializing for the case of the RandomAccess marker interface being retained if it was there originally */ @Suppress("UNCHECKED_CAST") fun <T> List<T>.toImmutable(): List<T> { val copy = when (this) { is RandomAccess -> ArrayList<T>(this) else -> this.toList() } return when (copy) { is RandomAccess -> ReadOnlyRandomAccessList(copy) else -> ReadOnlyList(copy) } } 

Puede ver la idea y extrapolar para crear las classs que faltan de este código que repite los patrones para otros types a los que se hace reference. O vea el código completo aquí:

https://github.com/kohesive/klutter/blob/master/core-jdk6/src/main/kotlin/uy/klutter/core/common/Immutable.kt

Y con testings que muestran algunos de los trucos que permitieron modificaciones antes, pero ahora no, junto con las conversiones bloqueadas y las llamadas que usan estas envolturas.

https://github.com/kohesive/klutter/blob/master/core-jdk6/src/test/kotlin/uy/klutter/core/collections/TestImmutable.kt

  • Dagger2 @Nullable anotación con Kotlin
  • Filtrar por la propiedad de la class enum Kotlin
  • Implementando la interfaz Java - Kotlin
  • Android: Kotlin: webView personalizado: no se puede invocar como una function. La function 'invoke ()' no se encuentra
  • Crear método con un parámetro genérico para acciones repetidas
  • Proyecto Android kotlin construido atascado en la aplicación: tarea kaptDebugKotlin
  • Kotlin y Gradle sin Maven Central?
  • La propiedad debe inicializarse o ser abstracta
  • Color de background e image arrastrando en Scroll en recyclerView Android usando Group Adaptor
  • isInitialized - El campo de respaldo de var tardinit no está accesible en este punto
  • No llame a setOnClickListener para un AdapterView