Iterable y Secuencia de Kotlin se ven exactamente iguales. ¿Por qué se requieren dos tipos?
lazy-sequences (2)
Ambas interfaces definen solo un método
public operator fun iterator(): Iterator<T>
La documentación dice que la
Sequence
debe ser perezosa.
¿Pero no es
Iterable
perezoso también (a menos que esté respaldado por una
Collection
)?
Completando la respuesta de la tecla de acceso rápido:
Es importante notar cómo Secuencia e Iterable itera a través de sus elementos:
Ejemplo de secuencia:
list.asSequence()
.filter { field ->
Log.d("Filter", "filter")
field.value > 0
}.map {
Log.d("Map", "Map")
}.forEach {
Log.d("Each", "Each")
}
Resultado del registro:
filtro - Mapa - Cada uno; filtro - Mapa - Cada
Ejemplo iterable:
list.filter { field ->
Log.d("Filter", "filter")
field.value > 0
}.map {
Log.d("Map", "Map")
}.forEach {
Log.d("Each", "Each")
}
filtro - filtro - Mapa - Mapa - Cada - Cada
La diferencia clave radica en la semántica y la implementación de las funciones de extensión stdlib para
Iterable<T>
y
Sequence<T>
.
-
Para
Sequence<T>
, las funciones de extensión funcionan de manera perezosa cuando sea posible, de manera similar a las operaciones intermedias de Java Streams. Por ejemplo,Sequence<T>.map { ... }
devuelve otraSequence<R>
y en realidad no procesa los elementos hasta que se llama a una operación de terminal comotoList
ofold
.Considera este código:
val seq = sequenceOf(1, 2) val seqMapped: Sequence<Int> = seq.map { print("$it "); it * it } // intermediate print("before sum ") val sum = seqMapped.sum() // terminal
Imprime:
before sum 1 2
Sequence<T>
está diseñada para un uso diferido y una canalización eficiente cuando se desea reducir el trabajo realizado en operaciones de terminal tanto como sea posible, lo mismo que para Java Streams. Sin embargo, la pereza introduce cierta sobrecarga, lo que no es deseable para las transformaciones simples comunes de colecciones más pequeñas y las hace menos productivas.En general, no hay una buena manera de determinar cuándo es necesario, por lo que en Kotlin stdlib la pereza se hace explícita y se extrae a la interfaz
Sequence<T>
para evitar su uso en todos losIterable
por defecto. -
Para
Iterable<T>
, por el contrario, las funciones de extensión con semántica de operación intermedia funcionan con entusiasmo, procesan los elementos de inmediato y devuelven otroIterable
. Por ejemplo,Iterable<T>.map { ... }
devuelve unaList<R>
con los resultados del mapeo.El código equivalente para Iterable:
val lst = listOf(1, 2) val lstMapped: List<Int> = lst.map { print("$it "); it * it } print("before sum ") val sum = lstMapped.sum()
Esto imprime:
1 2 before sum
Como se dijo anteriormente,
Iterable<T>
no es perezoso por defecto, y esta solución se muestra bien: en la mayoría de los casos tiene una buena localidad de referencia , aprovechando el caché de la CPU, la predicción, la captación previa, etc., de modo que incluso la copia múltiple de un La colección todavía funciona lo suficientemente bien y funciona mejor en casos simples con colecciones pequeñas.Si necesita más control sobre la canalización de evaluación, hay una conversión explícita a una secuencia diferida con la función
Iterable<T>.asSequence()
.