Sobes.tech
Middle

Como trabalhar com streams em Kotlin?

sobes.tech IA

Resposta da IA

Em Kotlin, para trabalhar com fluxos de dados (equivalente à Java 8 Streams API), utilizam-se funções de extensão para coleções fornecidas pela biblioteca de funções padrão. Não é uma API separada, mas um conjunto de métodos em kotlin.collections.

As operações principais com fluxos dividem-se em:

  • Operações intermédias: Devolvem uma nova coleção e são "preguiçosas" (só se executam ao chamar uma operação terminal). Exemplos: filter, map, flatMap, distinct, sorted, take, drop.
  • Operações terminais: Finalizam a sequência de operações e devolvem um resultado (não uma coleção). Disparam a execução de todas as operações intermédias. Exemplos: forEach, reduce, fold, count, sum, average, min, max, toList, toSet, toMap.

Existe também o conceito de sequências (sequences), que fornece uma preguiça explícita. As sequências processam os elementos um a um conforme necessário, o que pode ser mais eficiente para coleções grandes ou cadeias longas de operações:

  • Criação de uma sequência: collection.asSequence()
  • Conversão de volta para coleção: sequence.toList()
// Exemplo de uso de funções padrão para processamento em fluxo
fun processCollection() {
    val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

    val evenSquares = numbers
        .filter { it % 2 == 0 } // Operação intermédia: filtrar números pares
        .map { it * it }     // Operação intermédia: elevar ao quadrado

    println(evenSquares) // Operação terminal: mostrar a coleção (executa filter e map)

    val sumOfOdd = numbers
        .filter { it % 2 != 0 } // Intermédia
        .sum()                // Terminal (calcula a soma dos ímpares)

    println(sumOfOdd)
}

// Exemplo de uso de sequências
fun processSequence() {
    val largeCollection = (1..1_000_000).toList()

    // Com coleções (não preguiçosas): filter e map executam-se para todos os elementos imediatamente
    val resultCollection = largeCollection
        .filter { it % 2 == 0 }
        .map { it * 2 }
        .take(10) // take(10) executa-se após filter e map para todos

    println("Tamanho da coleção resultado: ${resultCollection.size}")

    // Com sequências (preguiçosas): filter, map e take executam-se elemento por elemento
    val resultSequence = largeCollection.asSequence()
        .filter { it % 2 == 0 } // filter executa para cada elemento em ordem
        .map { it * 2 }     // map executa para cada elemento que passa o filtro
        .take(10)         // take(10) para o processamento após 10 elementos

    println("Tamanho da sequência resultado: ${resultSequence.toList().size}") // toList() dispara a execução da sequência
}

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

Tabela comparativa de funções padrão e sequências relativamente à preguiça:

Característica Funções padrão de coleções Sequências
Preguiça Preguiça implícita para operações intermédias, mas a operação terminal processa toda a cadeia intermédia Preguiça explícita: as operações executam-se elemento por elemento conforme necessário
Eficiência Pode ser menos eficiente para grandes coleções e cadeias longas de operações devido às coleções intermédias Mais eficiente para grandes coleções e cadeias longas de operações
Aplicação Adequado para coleções pequenas e médias Adequado para grandes coleções ou operações que podem ser interrompidas cedo (take, find, first, any, all, none)

O uso de funções padrão em coleções é geralmente suficiente e mais conciso. Sequências são recomendadas quando é necessária uma preguiça explícita para otimizar o desempenho ao trabalhar com grandes volumes de dados.