Sobes.tech
Middle

Cum să lucrezi cu fluxurile (streams) în Kotlin?

sobes.tech AI

Răspuns de la AI

În Kotlin, pentru lucrul cu fluxurile de date (analog Java 8 Streams API), se utilizează funcții de extensie pentru colecții, oferite de biblioteca de funcții standard. Acestea nu sunt un API separat, ci un set de metode din kotlin.collections.

Operațiunile principale cu fluxurile sunt împărțite în:

  • Operațiuni intermediare: Returnează o colecție nouă și sunt "leneșe" (se execută doar la apelarea unei operațiuni terminale). Exemple: filter, map, flatMap, distinct, sorted, take, drop.
  • Operațiuni terminale: Încheie secvența de operațiuni și returnează rezultatul (nu o colecție). Acționează ca trigger pentru executarea tuturor operațiunilor intermediare. Exemple: forEach, reduce, fold, count, sum, average, min, max, toList, toSet, toMap.

Există, de asemenea, conceptul de fluxuri secvențiale (sequences), care asigură o lenevie explicită. Sequences procesează elementele unul câte unul, după necesitate, ceea ce poate fi mai eficient pentru colecții mari sau lanțuri lungi de operațiuni:

  • Crearea unei secvențe: collection.asSequence()
  • Conversia înapoi în colecție: sequence.toList()
// Exemplu de utilizare a funcțiilor standard pentru procesarea fluxurilor
fun processCollection() {
    val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

    val evenSquares = numbers
        .filter { it % 2 == 0 } // Operațiune intermediară: filtrăm numerele pare
        .map { it * it }     // Operațiune intermediară: ridicăm la pătrat

    println(evenSquares) // Operațiune terminală: afișează colecția (execută filtrarea și map)

    val sumOfOdd = numbers
        .filter { it % 2 != 0 } // Operațiune intermediară
        .sum()                // Operațiune terminală: calculează suma numerelor impare

    println(sumOfOdd)
}

// Exemplu de utilizare a secvențelor
fun processSequence() {
    val largeCollection = (1..1_000_000).toList()

    // Cu colecții (ne leneșe): filter și map se vor executa pentru toți elementele
    val resultCollection = largeCollection
        .filter { it % 2 == 0 }
        .map { it * 2 }
        .take(10) // take(10) se execută după filtrare și map pentru toate

    println("Result Collection size: ${resultCollection.size}")

    // Cu secvențe (leneșe): filter, map și take se execută element cu element
    val resultSequence = largeCollection.asSequence()
        .filter { it % 2 == 0 } // filter se execută pentru fiecare element
        .map { it * 2 }     // map se execută pentru fiecare element trecut prin filtru
        .take(10)         // take(10) oprește procesarea după 10 elemente

    println("Result Sequence size: ${resultSequence.toList().size}") // toList() declanșează executarea secvenței
}

fun main() {
    processCollection()
    processSequence()
}

Tabel comparativ între funcțiile standard și sequences în ceea ce privește lenevia:

Caracteristică Funcțiile standard ale colecțiilor Sequences
Lenevie Lenevie implicită pentru operațiunile intermediare, dar operațiunea terminală procesează întreg lanțul de operațiuni Lenevie explicită: operațiunile se execută element cu element după necesitate
Eficiență Poate fi mai puțin eficientă pentru colecții mari și lanțuri lungi de operațiuni din cauza colecțiilor intermediare Mai eficient pentru colecții mari și lanțuri lungi de operațiuni
Aplicare Potrivit pentru colecții mici și medii Potrivit pentru colecții mari sau operațiuni care pot fi întrerupte devreme (take, find, first, any, all, none)

Utilizarea funcțiilor standard pe colecții este de cele mai multe ori suficientă și mai concisă. Sequences sunt recomandate atunci când este nevoie de lenevie explicită pentru a optimiza performanța în lucrul cu volume mari de date.