Kotlin: Operações de Coleção do Kotlin Explicadas

Última atualização: 2026-08-26

As operações de coleção do Kotlin permitem que Charlie substitua loops imperativos por pipelines declarativos — uma linha substitui 15 linhas de loop for, com intenção mais clara.

1. O que Você Aprenderá


2. A História Real de um Arquiteto

(1) Ponto de Dor: OOM ao Processar Milhões de Pedidos

Charlie usou List para processar milhões de registros de pedidos. Cada operação intermediária criava uma nova coleção, consumindo 3GB de memória e causando OOM.

(2) A Solução com Avaliação Preguiçosa do Sequence

KOTLIN
// Eager: cria coleções intermediárias em cada etapa
orders.map { enrich(it) }       // Coleção 1: 1M elementos
      .filter { it.total > 100 } // Coleção 2: ~500K elementos
      .toList()                   // Coleção 3

// Preguiçoso: processa um elemento por todo o pipeline
orders.asSequence()
      .map { enrich(it) }
      .filter { it.total > 100 }
      .toList()  // Apenas 1 coleção final

Sequence é como uma linha de montagem: cada elemento passa por todo o pipeline antes do próximo começar — sem coleções intermediárias necessárias.


3. Operadores Principais

(1) map — Transformar

KOTLIN
val orders = listOf(
    Order("ORD-001", 299.99, "Alice"),
    Order("ORD-002", 1_500.00, "Bob")
)

// Transformar cada elemento
val ids = orders.map { it.id }                // [ORD-001, ORD-002]
val summaries = orders.map { "${it.id}: \$${it.total} USD" }

// mapIndexed: com índice
orders.mapIndexed { i, order -> "[${i + 1}] ${order.id}" }

// mapNotNull: transformar + filtrar nulos
val emails = orders.mapNotNull { it.customerEmail }

(2) filter — Filtrar

KOTLIN
// Filtrar por predicado
val highValue = orders.filter { it.total > 1_000 }
val pending = orders.filter { it.status == "PENDING" }

// filterNot: filtro inverso
val active = orders.filterNot { it.status == "CANCELLED" }

// filterIndexed: com índice
orders.filterIndexed { i, _ -> i % 2 == 0 }  // Pedidos de índice par

(3) flatMap — Transformar e Achatar

KOTLIN
data class Customer(val name: String, val orders: List<Order>)

val customers = listOf(
    Customer("Alice", listOf(Order("ORD-001", 299.99), Order("ORD-002", 150.0))),
    Customer("Bob", listOf(Order("ORD-003", 1_500.00)))
)

// Map + Achatar em um passo
val allOrders = customers.flatMap { it.orders }
// [Order(ORD-001, 299.99), Order(ORD-002, 150.0), Order(ORD-003, 1500.0)]

(4) groupBy — Agrupar

KOTLIN
// Agrupar por chave
val byCustomer: Map<String, List<Order>> = orders.groupBy { it.customer }

// Agrupar por com transformação de valor
val totalsByCustomer = orders.groupBy(
    keySelector = { it.customer },
    valueTransform = { it.total }
)
// {Alice=[299.99], Bob=[1500.0]}

(5) associate — Associar a Map

KOTLIN
// Criar map a partir de lista
val orderMap = orders.associate { it.id to it }
// {ORD-001=Order(...), ORD-002=Order(...)}

// associateBy: seletor de chave
val byId = orders.associateBy { it.id }

// associateBy com transformação de valor
val totalsById = orders.associateBy(
    keySelector = { it.id },
    valueTransform = { it.total }
)

(6) Referência Rápida de Operadores

Operador Função Entrada → Saída Analogia SQL
map Transformar List<A>List<B> SELECT
filter Filtrar List<T>List<T> WHERE
flatMap Transformar e achatar List<A>List<B> JOIN + SELECT
groupBy Agrupar List<T>Map<K, List<T>> GROUP BY
associate Mapear List<T>Map<K, V> -
distinct Deduplicar List<T>List<T> DISTINCT
sortedBy Ordenar List<T>List<T> ORDER BY

4. Pipeline de Operações de Coleção

100%
flowchart LR
    A[Pedidos<br/>1M registros] --> B[filter<br/>total > 1000]
    B --> C[map<br/>extrair cliente]
    C --> D[distinct<br/>clientes únicos]
    D --> E[groupBy<br/>por região]
    E --> F[Resultado<br/>Map de clientes]

5. Avaliação Preguiçosa com Sequence

(1) Eager vs Preguiçoso

KOTLIN
// Eager (List): cada etapa cria nova coleção
val result = orders
    .map { println("map: ${it.id}"); it.copy(total = it.total * 0.9) }
    .filter { println("filter: ${it.id}"); it.total > 100 }
    .take(2)
    .toList()

// Preguiçoso (Sequence): processa um elemento por vez
val result2 = orders.asSequence()
    .map { println("seq-map: ${it.id}"); it.copy(total = it.total * 0.9) }
    .filter { println("seq-filter: ${it.id}"); it.total > 100 }
    .take(2)
    .toList()
// Sequence só processa elementos até que take(2) seja satisfeito

(2) Quando Usar Sequence

Cenário Usar List Usar Sequence
Tamanho dos dados < 10.000 > 10.000
Etapas de operação 1-2 etapas 3+ etapas
Tamanho do resultado intermediário Próximo ao tamanho original Significativamente reduzido
Múltiplas travessias necessárias Sim Não

(3) Desempenho List vs Sequence

Dimensão List (Eager) Sequence (Preguiçoso)
Coleções intermediárias Criadas em cada etapa Não criadas
Memória O(n × etapas) O(1)
Operações de curto-circuito Não otimizadas Otimizadas (ex.: take só processa N)
Primeiro resultado Espera pipeline completo Disponível imediatamente

6. Operações de Agregação

(1) fold / reduce

KOTLIN
// fold: com valor inicial
val totalRevenue = orders.fold(0.0) { acc, order -> acc + order.total }

// reduce: primeiro elemento como valor inicial
val maxOrder = orders.reduce { max, order ->
    if (order.total > max.total) order else max
}

// foldRight: do final ao início
val reversed = orders.foldRight(emptyList<Order>()) { order, acc -> acc + order }

(2) Agregação de Conveniência

KOTLIN
val total = orders.sumOf { it.total }
val avg = orders.map { it.total }.average()
val max = orders.maxByOrNull { it.total }
val min = orders.minByOrNull { it.total }
val count = orders.count { it.total > 1_000 }

// Ordenação
val sorted = orders.sortedByDescending { it.total }
val top3 = orders.sortedByDescending { it.total }.take(3)

(3) fold vs reduce

Dimensão fold reduce
Valor inicial Deve fornecer Primeiro elemento implícito
Coleção vazia Seguro (retorna valor inicial) Lança exceção
Tipo de retorno Pode diferir do tipo do elemento Mesmo tipo do elemento
Recomendação ⭐⭐⭐ ⭐⭐

7. Coleções Imutáveis vs Mutáveis

(1) Interfaces Somente Leitura e Mutáveis

KOTLIN
// Somente leitura (interface imutável)
val list: List<Order> = listOf(Order("ORD-001", 299.99, "Alice"))

// Mutável
val mutableList: MutableList<Order> = mutableListOf()
mutableList.add(Order("ORD-002", 1_500.00, "Bob"))

// Visão somente leitura de lista mutável
val readOnly: List<Order> = mutableList  // OK: MutableList estende List
// readOnly.add(...)  // ERRO: List não tem método add
mutableList.add(Order("ORD-003", 45.50, "Charlie"))  // Muda a visão readOnly!

(2) Cópia Defensiva

KOTLIN
class OrderProcessor(private val _orders: MutableList<Order>) {
    // Cópia defensiva: expor visão imutável
    val orders: List<Order> get() = _orders.toList()

    // Ou usar visão imutável (sem cópia, mas pode ser convertido de volta)
    val ordersView: List<Order> get() = _orders.toList()
}

(3) Comparação de Tipos de Coleção

Tipo Interface Somente Leitura Interface Mutável Funções de Fábrica
List List MutableList listOf / mutableListOf
Set Set MutableSet setOf / mutableSetOf
Map Map MutableMap mapOf / mutableMapOf

8. Exemplo Completo: Pipeline de Processamento de Milhões de Pedidos

KOTLIN
// ============================================
// OrderProcessor - Pipeline de Coleção
// Recurso: Processar pedidos com operadores funcionais
// ============================================

data class Order(val id: String, val total: Double, val status: String, val customer: String, val region: String)

fun main() {
    // Simular dados de pedidos
    val orders = listOf(
        Order("ORD-001", 299.99, "CONFIRMED", "Alice", "US"),
        Order("ORD-002", 15_000.00, "CONFIRMED", "Bob", "EU"),
        Order("ORD-003", 2_500.00, "PENDING", "Charlie", "US"),
        Order("ORD-004", 45.50, "CANCELLED", "Alice", "ASIA"),
        Order("ORD-005", 8_900.00, "CONFIRMED", "Bob", "EU"),
        Order("ORD-006", 1_200.00, "SHIPPED", "Charlie", "US"),
        Order("ORD-007", 350.00, "CONFIRMED", "Alice", "ASIA"),
        Order("ORD-008", 22_000.00, "PENDING", "Bob", "EU"),
        Order("ORD-009", 750.00, "CONFIRMED", "Charlie", "US"),
        Order("ORD-010", 4_500.00, "SHIPPED", "Alice", "US")
    )

    // Pipeline 1: Pedidos confirmados de alto valor
    println("=== Pedidos Confirmados de Alto Valor ===")
    orders.filter { it.status == "CONFIRMED" && it.total > 1_000 }
        .sortedByDescending { it.total }
        .forEach { println("  ${it.id}: \$${it.total} USD (${it.customer})") }

    // Pipeline 2: Receita por região
    println("\n=== Receita por Região ===")
    orders.filter { it.status != "CANCELLED" }
        .groupBy { it.region }
        .mapValues { (_, list) -> list.sumOf { it.total } }
        .forEach { (region, revenue) -> println("  $region: \$$revenue USD") }

    // Pipeline 3: Principais clientes por contagem de pedidos e receita
    println("\n=== Resumo de Clientes ===")
    orders.filter { it.status != "CANCELLED" }
        .groupBy { it.customer }
        .map { (customer, list) ->
            val count = list.size
            val total = list.sumOf { it.total }
            val avg = list.map { it.total }.average()
            "$customer: $count pedidos, \$${total} USD total, \$${"%.2f".format(avg)} média"
        }
        .forEach { println("  $it") }

    // Pipeline 4: Usando Sequence para processamento eficiente
    println("\n=== Top 3 Pedidos (Sequence) ===")
    orders.asSequence()
        .filter { it.status != "CANCELLED" }
        .sortedByDescending { it.total }
        .take(3)
        .forEach { println("  ${it.id}: \$${it.total} USD") }

    // Agregação: fold para construir string de resumo
    val summary = orders
        .filter { it.status != "CANCELLED" }
        .fold("Resumo de Pedidos: ") { acc, order -> "$acc\n  ${order.id} (\$${order.total} USD)" }
    println("\n${summary}")
    println("Total de pedidos: ${orders.size}, Ativos: ${orders.count { it.status != "CANCELLED" }}")
}

Saída:

TEXT 📖 Somente leitura
=== Pedidos Confirmados de Alto Valor ===
  ORD-005: $8900.0 USD (Bob)
  ORD-002: $15000.0 USD (Bob)

=== Receita por Região ===
  US: $7599.99 USD
  EU: $46400.0 USD
  ASIA: $350.0 USD

=== Resumo de Clientes ===
  Alice: 2 pedidos, $3649.99 USD total, $1824.995 média
  Bob: 3 pedidos, $46400.0 USD total, $15466.666666666666 média
  Charlie: 3 pedidos, $4450.0 USD total, $1483.3333333333333 média

=== Top 3 Pedidos (Sequence) ===
  ORD-008: $22000.0 USD (Bob)
  ORD-002: $15000.0 USD (Bob)
  ORD-005: $8900.0 USD (Bob)

Resumo de Pedidos: 
  ORD-001 ($299.99 USD)
  ORD-002 ($15000.0 USD)
  ORD-003 ($2500.0 USD)
  ORD-005 ($8900.0 USD)
  ORD-006 ($1200.0 USD)
  ORD-007 ($350.0 USD)
  ORD-008 ($22000.0 USD)
  ORD-009 ($750.0 USD)
  ORD-010 ($4500.0 USD)
Total de pedidos: 10, Ativos: 9

9. Exemplos práticos rápidos

▶ Exemplo: List imutável e mutável

KOTLIN
// List imutável (apenas leitura)
val immutableList = listOf("ORD-001", "ORD-002", "ORD-003")
println("Imutável: $immutableList")
// immutableList.add("ORD-004") // erro de compilação

// List mutável
val mutableList = mutableListOf("a", "b", "c")
mutableList.add("d")
mutableList.removeAt(0)
mutableList[0] = "AA"
println("Mutável: $mutableList")

// Operações similares a set
val seen = mutableSetOf<Int>()
listOf(1, 2, 2, 3, 3, 3, 4).forEach { seen.add(it) }
println("Únicos: $seen")

Saída:

TEXT 📖 Somente leitura
Imutável: [ORD-001, ORD-002, ORD-003]
Mutável: [AA, c, d]
Únicos: [1, 2, 3, 4]

▶ Exemplo: Map básico

KOTLIN
// Map imutável
val prices = mapOf(
    "ORD-001" to 299.99,
    "ORD-002" to 1500.00,
    "ORD-003" to 50.00
)

println("Preço de ORD-001: ${prices["ORD-001"]}")
println("Preço de ORD-999: ${prices["ORD-999"] ?: 0.0}")

// Iteração
prices.forEach { (id, price) -> println("$id -> \$$price") }

// Map mutável
val cart = mutableMapOf<String, Int>()
cart["apple"] = 3
cart["banana"] = 5
println("Carrinho: $cart")

// Operações atomicas
cart.merge("apple", 2) { old, new -> old + new }
println("Após merge: $cart")

Saída:

TEXT 📖 Somente leitura
Preço de ORD-001: 299.99
Preço de ORD-999: 0.0
ORD-001 -> $299.99
ORD-002 -> $1500.0
ORD-003 -> $50.0
Carrinho: {apple=3, banana=5}
Após merge: {apple=5, banana=5}

▶ Exemplo: filter, map, flatMap

KOTLIN
val orders = listOf(
    mapOf("id" to "ORD-001", "amount" to 299, "status" to "CONFIRMED"),
    mapOf("id" to "ORD-002", "amount" to 1500, "status" to "PENDING"),
    mapOf("id" to "ORD-003", "amount" to 50, "status" to "CANCELLED"),
    mapOf("id" to "ORD-004", "amount" to 2300, "status" to "CONFIRMED"),
    mapOf("id" to "ORD-005", "amount" to 500, "status" to "PENDING")
)

// Filtro
val confirmed = orders.filter { it["status"] == "CONFIRMED" }
println("Confirmados: ${confirmed.map { it["id"] }}")

// Map - projetar
val ids = orders.map { it["id"] }
println("Todos IDs: $ids")

// Filter + map
val confirmedAmounts = orders.filter { it["status"] == "CONFIRMED" }
    .map { it["amount"] as Int * 1.08 }
println("Valores confirmados c/ imposto: $confirmedAmounts")

// flatMap
val users = listOf(
    mapOf("name" to "Alice", "roles" to listOf("admin", "user")),
    mapOf("name" to "Bob", "roles" to listOf("user"))
)
val allRoles = users.flatMap { it["roles"] as List<String> }
println("Todos papéis: $allRoles")

Saída:

TEXT 📖 Somente leitura
Confirmados: [ORD-001, ORD-004]
Todos IDs: [ORD-001, ORD-002, ORD-003, ORD-004, ORD-005]
Valores confirmados c/ imposto: [323, 2484]
Todos papéis: [admin, user, user]

▶ Exemplo: reduce, fold, groupBy

KOTLIN
val numbers = listOf(1, 2, 3, 4, 5)

// reduce - acumulação, sem valor inicial
val product = numbers.reduce { acc, n -> acc * n }
println("Produto: $product")

// fold - com valor inicial (permite tipo diferente)
val sumAsString = numbers.fold("0") { acc, n -> "$acc + $n" }
println("Soma como string: $sumAsString = ${numbers.sum()}")

// groupBy
val words = listOf("apple", "banana", "avocado", "blueberry", "cherry")
val byFirstLetter = words.groupBy { it[0] }
byFirstLetter.forEach { (letter, list) ->
    println("$letter: $list")
}

Saída:

TEXT 📖 Somente leitura
Produto: 120
Soma como string: 0 + 1 + 2 + 3 + 4 + 5 = 15
a: [apple, avocado]
b: [banana, blueberry]
c: [cherry]

▶ Exemplo: aggregate e sumBy

KOTLIN
data class Sale(val product: String, val amount: Double)

val sales = listOf(
    Sale("Coffee", 4.50),
    Sale("Tea", 3.00),
    Sale("Cake", 8.00),
    Sale("Cookie", 2.50)
)

// sumOf
val total = sales.sumOf { it.amount }
println("Total: \$$total")

// sumOf com transformação diferente
val totalCents = sales.sumOf { (it.amount * 100).toInt() }
println("Total em centavos: $totalCents")

// Média
val avg = sales.map { it.amount }.average()
println("Média: \$$avg")

// Min/Max
val min = sales.minByOrNull { it.amount }
val max = sales.maxByOrNull { it.amount }
println("Mínimo: $min")
println("Máximo: $max")

Saída:

TEXT 📖 Somente leitura
Total: $18.0
Total em centavos: 1800
Média: $4.5
Mínimo: Sale(product=Cookie, amount=2.5)
Máximo: Sale(product=Cake, amount=8.0)

▶ Exemplo: Sequence vs List

KOTLIN
// List: eager - cada operação cria uma lista intermediária
val numbers = (1..10_000_000).toList()

val timeList = System.currentTimeMillis()
val sumList = numbers
    .filter { it % 2 == 0 }
    .map { it * 2 }
    .take(5)
    .sum()
val durationList = System.currentTimeMillis() - timeList

// Sequence: lazy - processo item por item, sem coleções intermediárias
val timeSeq = System.currentTimeMillis()
val sumSeq = numbers.asSequence()
    .filter { it % 2 == 0 }
    .map { it * 2 }
    .take(5)
    .sum()
val durationSeq = System.currentTimeMillis() - timeSeq

println("Soma (List): $sumList em ${durationList}ms")
println("Soma (Sequence): $sumSeq em ${durationSeq}ms")

// Quando usar cada um:
// - List: pequeno dataset, muitas chamadas terminais
// - Sequence: grande dataset, várias transformações em cadeia

Saída:

TEXT 📖 Somente leitura
Soma (List): 120 em XXms
Soma (Sequence): 120 em YYms

▶ Exemplo: zip, partition, chunked

KOTLIN
val names = listOf("Ana", "Bruno", "Carla")
val scores = listOf(85, 92, 78)
val grades = listOf("B", "A", "C")

// zip: combinar duas listas
val combined = names.zip(scores)
println("Combinado: $combined")

// zip com transform
val summary = names.zip(scores) { n, s -> "$n: $s" }
println("Resumo: $summary")

// Três listas combinadas: zipTransversal
val fullReport = names.zip(scores).zip(grades) { pair, g ->
    "${pair.first}: ${pair.second} ($g)"
}
println("Relatório: $fullReport")

// chunked: dividir em grupos
val nums = (1..10).toList()
val chunks = nums.chunked(3)
println("Chunks: $chunks")

// windowed: janelas deslizantes
val threeSum = nums.windowed(3) { it.sum() }
println("Soma de janelas: $threeSum")

Saída:

TEXT 📖 Somente leitura
Combinado: [(Ana, 85), (Bruno, 92), (Carla, 78)]
Resumo: [Ana: 85, Bruno: 92, Carla: 78]
Relatório: [Ana: 85 (B), Bruno: 92 (A), Carla: 78 (C)]
Chunks: [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
Soma de janelas: [6, 9, 12, 15, 18, 21, 24, 27]

❓ Perguntas Frequentes

P: Qual a diferença entre operações de coleção do Kotlin e Java Stream? R: Operações de coleção do Kotlin são mais concisas (palavra-chave it, lambdas finais), sem necessidade de conversões stream()/collect(), e Sequence é similar ao Stream, porém mais leve.

P: Quando devo usar Sequence? R: Use Sequence quando os dados são grandes (>10.000), há muitas etapas de operação (3+), ou você precisa de curto-circuito (take/first). Para dados pequenos, List é mais simples.

P: toList() copia dados? R: Sim. toList() cria uma nova lista — é uma cópia defensiva. Se você precisa apenas de uma visão somente leitura sem cópia, basta usar uma referência de interface List.

P: fold ou reduce — qual escolher? R: Prefira fold porque permite especificar um valor inicial e é seguro para coleções vazias. reduce lança exceção em coleções vazias.

P: A List do Kotlin é verdadeiramente imutável? R: Não. A List do Kotlin é uma interface somente leitura (não pode ser modificada através dela), mas a implementação subjacente pode ser mutável. Verdadeira imutabilidade requer criar uma cópia com toList().

P: Há diferença entre flatMap e map + flatten? R: Funcionalmente equivalentes; flatMap é mais eficiente (feito em um passo). É a abreviação combinada de map + flatten.


📖 Resumo


📝 Exercícios

  1. Iniciante (⭐): Use filter e map para extrair IDs de pedidos com valores > 1000 USD de uma lista de pedidos. Dica: orders.filter { }.map { }
  2. Intermediário (⭐⭐): Use groupBy + mapValues para calcular o gasto total de cada cliente, ordenado por valor em ordem decrescente. Dica: groupBy { it.customer }.mapValues { }
  3. Avançado (⭐⭐⭐): Use Sequence para implementar um pipeline de processamento preguiçoso para milhões de pedidos: filter → map → take(100), e imprima o número de elementos realmente processados. Dica: use um contador em onEach

← Anterior | Próximo →

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%