Sobes.tech
Middle

Jak pracować z strumieniami (streams) w Kotlinie?

sobes.tech AI

Odpowiedź od AI

W Kotlinie do pracy z przepływami danych (odpowiednik Java 8 Streams API) używa się funkcji rozszerzających dla kolekcji, dostarczanych przez bibliotekę funkcji standardowych. Nie jest to osobne API, lecz zbiór metod w kotlin.collections.

Główne operacje na przepływach dzielą się na:

  • Operacje pośrednie: Zwracają nową kolekcję i są "leniwe" (wykonują się tylko po wywołaniu operacji końcowej). Przykłady: filter, map, flatMap, distinct, sorted, take, drop.
  • Operacje końcowe: Kończą ciąg operacji i zwracają wynik (nie kolekcję). Wywołują wykonanie wszystkich operacji pośrednich. Przykłady: forEach, reduce, fold, count, sum, average, min, max, toList, toSet, toMap.

Istnieje także koncepcja sekwencji (sequences), które zapewniają jawne lenistwo. Sekwencje przetwarzają elementy pojedynczo w razie potrzeby, co może być bardziej wydajne dla dużych kolekcji lub długich łańcuchów operacji:

  • Tworzenie sekwencji: collection.asSequence()
  • Konwersja z powrotem do kolekcji: sequence.toList()
// Przykład użycia funkcji standardowych do przetwarzania strumieniowego
fun processCollection() {
    val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

    val evenSquares = numbers
        .filter { it % 2 == 0 } // Operacja pośrednia: filtrujemy liczby parzyste
        .map { it * it }     // Operacja pośrednia: podnosimy do kwadratu

    println(evenSquares) // Operacja końcowa: wyświetlamy kolekcję (wykonuje filter i map)

    val sumOfOdd = numbers
        .filter { it % 2 != 0 } // Pośrednia
        .sum()                // Końcowa (oblicza sumę nieparzystych)

    println(sumOfOdd)
}

// Przykład użycia sekwencji
fun processSequence() {
    val largeCollection = (1..1_000_000).toList()

    // Z kolekcjami (nie leniwymi): filter i map wykonują się od razu dla wszystkich elementów
    val resultCollection = largeCollection
        .filter { it % 2 == 0 }
        .map { it * 2 }
        .take(10) // take(10) wykonuje się po filter i map dla wszystkich

    println("Rozmiar wynikowej kolekcji: ${resultCollection.size}")

    // Z sekwencjami (leniwe): filter, map i take wykonują się element po elemencie
    val resultSequence = largeCollection.asSequence()
        .filter { it % 2 == 0 } // filter wykonuje się dla każdego elementu w kolejności
        .map { it * 2 }     // map wykonuje się dla każdego elementu, który przeszedł filtr
        .take(10)         // take(10) zatrzymuje przetwarzanie po 10 elementach

    println("Rozmiar wynikowej sekwencji: ${resultSequence.toList().size}") // toList() uruchamia wykonanie sekwencji
}

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

Porównanie funkcji standardowych i sekwencji pod względem lenistwa:

Cecha Funkcje standardowe kolekcji Sekwencje
Lenistwo Domyślne lenistwo dla operacji pośrednich, ale operacja końcowa przetwarza cały łańcuch pośredni Jawne lenistwo: operacje wykonują się element po elemencie w razie potrzeby
Wydajność Może być mniej wydajne dla dużych kolekcji i długich łańcuchów operacji z powodu kolekcji pośrednich Bardziej wydajne dla dużych kolekcji i długich łańcuchów operacji
Zastosowanie Odpowiednie dla małych i średnich kolekcji Odpowiednie dla dużych kolekcji lub operacji, które można przerwać wcześnie (take, find, first, any, all, none)

Użycie funkcji standardowych na kolekcjach jest zazwyczaj wystarczające i bardziej zwięzłe. Sekwencje zaleca się, gdy potrzebne jest jawne lenistwo w celu optymalizacji wydajności przy pracy z dużymi ilościami danych.