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.