recorrer - Kotlin y colecciones inmutables?
por que string es inmutable (5)
Como puede ver en otras respuestas, Kotlin tiene interfaces de solo lectura para colecciones mutables que le permiten ver una colección a través de una lente de solo lectura. Pero la colección se puede omitir a través de la transmisión o manipular desde Java. Pero en el código cooperativo de Kotlin que está bien, la mayoría de los usos no necesitan colecciones verdaderamente inmutables y si su equipo evita la conversión a la forma mutable de la colección, entonces tal vez no necesite colecciones completamente inmutables.
Las colecciones de Kotlin permiten mutaciones de copia en cambio, así como mutaciones perezosas.
Entonces, para responder parte de sus preguntas, cosas como
filter
,
map
,
map
flatmap
, operadores
+
-
todos crean copias cuando se usan contra colecciones no perezosas.
Cuando se usan en una
Sequence
, modifican los valores como la colección a medida que se accede y continúan siendo perezosos (lo que resulta en otra
Sequence
).
Aunque para una
Sequence
, llamar a algo como
toList
,
toSet
,
toMap
dará como resultado la copia final.
Al nombrar la convención, casi todo lo que comienza con es hacer una copia.
En otras palabras, la mayoría de los operadores le devuelven el mismo tipo con el que comenzó, y si ese tipo es "de solo lectura", recibirá una copia. Si ese tipo es perezoso, aplicará perezosamente el cambio hasta que exija la colección en su totalidad.
Algunas personas los quieren por otros motivos, como el procesamiento paralelo. En esos casos, podría ser mejor mirar colecciones de alto rendimiento diseñadas solo para esos fines. Y solo úselos en esos casos, no en todos los casos generales.
En el mundo de JVM, es difícil evitar interoperabilidad con bibliotecas que desean colecciones estándar de Java, y la conversión a / de estas colecciones agrega una gran carga 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 puede evitar querer colecciones inmutables, Kotlin trabaja fácilmente con cualquier cosa desde el espacio JVM:
- Guayaba ( https://github.com/google/guava )
- Dexx un puerto de las colecciones Scala a Java ( https://github.com/andrewoma/dexx ) con los ayudantes de Kotlin ( https://github.com/andrewoma/dexx/blob/master/kollection/README.md )
- Eclipse Collections (anteriormente GS-Collections), un rendimiento realmente alto, compatible con JDK, de alto rendimiento en procesamiento paralelo con variaciones inmutables y mutables (inicio: https://www.eclipse.org/collections/ y Github: https://github.com/eclipse/eclipse-collections )
- PCollections ( http://pcollections.org/ )
Además, el equipo de Kotlin está trabajando en colecciones inmutables de forma nativa para Kotlin, ese esfuerzo se puede ver aquí: https://github.com/Kotlin/kotlinx.collections.immutable
Existen muchos otros marcos de recopilación para todas las necesidades y limitaciones diferentes, Google es tu amigo para encontrarlos. No hay razón para que el equipo de Kotlin necesite reinventarlos para su biblioteca estándar. Tiene muchas opciones, y se especializan en diferentes cosas como rendimiento, uso de memoria, no boxeo, inmutabilidad, etc. "La elección es buena" ... por lo tanto, otras: HPCC , HPCC-RT , FastUtil , Koloboke , Trove y más ...
Incluso hay esfuerzos como Pure4J que, dado que Kotlin admite el procesamiento de Anotación ahora, tal vez pueda tener un puerto a Kotlin para ideales similares.
Estoy aprendiendo Kotlin y parece probable que quiera usarlo como mi idioma principal el próximo año. Sin embargo, sigo recibiendo investigaciones contradictorias de que Kotlin tiene o no colecciones inmutables y estoy tratando de averiguar si necesito usar Google Guava.
¿Puede alguien darme alguna orientación sobre esto? ¿Utiliza por defecto colecciones inmutables? ¿Qué operadores devuelven colecciones mutables o inmutables? Si no, ¿hay planes para implementarlos?
Es confuso pero hay tres tipos de inmutabilidad, no dos:
-
Mutable: se supone que debes cambiar la colección (
MutableList
de Kotlin) -
Solo lectura: se supone que NO debes cambiarlo (
List
de Kotlin) pero algo puede (emitirse a Mutable o cambiar de Java) - Inmutable: nadie puede cambiarlo (las colecciones inmutables de Guavas)
Entonces, en el caso (2)
List
es solo una interfaz que no tiene métodos de mutación, pero puede cambiar la instancia si la
MutableList
en
MutableList
.
Con Guava (caso (3)) está a salvo de que cualquiera cambie la colección, incluso con un yeso o de otro hilo.
Kotlin eligió ser de solo lectura para usar colecciones Java directamente, por lo que no hay gastos generales o conversión al usar colecciones Java.
Kotlin 1.0 no tendrá colecciones inmutables en la biblioteca estándar. Sin embargo, tiene interfaces de solo lectura y mutables. Y nada le impide utilizar bibliotecas de colecciones inmutables de terceros.
Los métodos en la interfaz de Kotlin''s
List
"admiten solo acceso de solo lectura a la lista", mientras que los métodos en su interfaz
MutableList
admiten "agregar y eliminar elementos".
Sin embargo, ambos son solo
interfaces
.
La interfaz de la
List
de Kotlin impone el acceso de solo lectura en tiempo de compilación en lugar de diferir tales comprobaciones en tiempo de ejecución como
java.util.Collections.unmodifiableList(java.util.List)
(que "devuelve una vista no modificable de la lista especificada ... [donde] intenta modificar la lista 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 los métodos como
add
no se pueden resolver (y no se compilarán),
mutableList
se puede mutar de forma natural y
add
on
immutableList
(de Google Guava) también se pueden resolver en tiempo de compilación, pero arroja una excepción en tiempo de ejecución
Todas las aserciones anteriores pasan con excepción de la última que da como resultado la
Exception in thread "main" java.lang.AssertionError: Expected <[1, 2, 3, 4]>, actual <[1, 2, 3]>.
es decir, mutamos con éxito una
List
solo lectura.
Tenga en cuenta que el uso de
listOf(...)
lugar de
arrayListOf(...)
devuelve una lista efectivamente inmutable, ya que no puede convertirla en ningún tipo de lista mutable.
Sin embargo, el uso de la interfaz de
List
para una variable no impide que se le
MutableList
una
MutableList
(
MutableList<E>
extiende la
List<E>
).
Finalmente, tenga en cuenta que una interfaz en Kotlin (así como en Java) no puede imponer la inmutabilidad ya que "no puede almacenar el estado" (ver Interfaces ). Como tal, si desea una colección inmutable, debe usar algo como los proporcionados por Google Guava.
Ver también ImmutableCollectionsExplained · Wiki de google / guava · GitHub
La
List
de Kotlin de la biblioteca estándar es de solo lectura:
interface List<out E> : Collection<E> (source)
Una colección genérica ordenada de elementos. Los métodos en esta interfaz solo admiten acceso de solo lectura a la lista; El acceso de lectura / escritura es compatible a través de la interfaz MutableList.
Parámetros
E - el tipo de elementos contenidos en la lista.
Como se mencionó, también está la
MutableList
interface MutableList<E> : List<E>, MutableCollection<E> (source)
Una colección genérica ordenada de elementos que admite agregar y eliminar elementos.
Parámetros
E - el tipo de elementos contenidos en la lista.
Debido a esto, Kotlin impone un comportamiento de solo lectura a través de sus interfaces, en lugar de lanzar Excepciones en tiempo de ejecución como lo hacen las implementaciones Java predeterminadas.
Del mismo modo, hay un
MutableCollection
,
MutableIterable
,
MutableIterator
,
MutableListIterator
,
MutableMap
y
MutableSet
, consulte la documentación de
stdlib
.
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 colecciones que cree sean inmutables. No pretende ser solo un anuncio de la biblioteca.
En la biblioteca de Klutter , hay nuevos envoltorios 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 rendimiento. Entonces no hay forma de convertir la colección, su iterador u otras colecciones que podría devolver a algo que podría modificarse. Se vuelven en efecto inmutables.
Lanzado Klutter
1.20.0
que agrega protectores inmutables para colecciones existentes, basado en una respuesta SO de @miensol, proporciona un delegado liviano alrededor de las colecciones 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 las subcolecciones como iterator, listIterator, entrySet, etc. Todas esas puertas están cerradas y utilizan la delegación de Kotlin para la mayoría de los métodos que no afecta el rendimiento. Simplemente llame amyCollection.asReadonly()
( protect ) omyCollection.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 clase real mientras se anulan los métodos 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 de ayuda 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 clases que faltan a partir de este código que repite los patrones para otros tipos referenciados. O vea el código completo aquí:
Y con las pruebas que muestran algunos de los trucos que permitieron modificaciones antes, pero que ahora no lo hacen, junto con los lanzamientos y llamadas bloqueados con estos envoltorios.