O que são Collections em Kotlin?

Collections (coleções) são estruturas que agrupam múltiplos elementos. Em Kotlin, existem três tipos principais: List (lista ordenada), Set (conjunto sem duplicatas) e Map (mapeamento chave-valor). Cada uma vem em versão imutável (padrão) e mutável.

Imagine que você está organizando uma estante de livros. Uma List seria como uma prateleira numerada, onde cada livro tem uma posição fixa. Um Set seria como uma coleção de selos únicos – não faz sentido ter dois selos idênticos. Já um Map funcionaria como um dicionário, onde cada palavra (chave) aponta para sua definição (valor).

A grande vantagem das collections em Kotlin sobre outras linguagens é a combinação de imutabilidade por padrão com um conjunto rico de funções de ordem superior que permitem transformar dados de forma declarativa e concisa.

Criando coleções

fun main() {
    // Imutáveis (somente leitura)
    val nomes = listOf("Ana", "Bruno", "Carla")
    val ids = setOf(1, 2, 3, 2) // {1, 2, 3} — sem duplicata
    val capitais = mapOf("SP" to "São Paulo", "RJ" to "Rio de Janeiro")

    // Mutáveis
    val tarefas = mutableListOf("Estudar", "Codar")
    tarefas.add("Descansar")
}

Operações funcionais

A grande força das coleções em Kotlin são as operações funcionais encadeadas:

data class Produto(val nome: String, val preco: Double, val categoria: String)

fun main() {
    val produtos = listOf(
        Produto("Notebook", 4500.0, "Tech"),
        Produto("Caderno", 25.0, "Papelaria"),
        Produto("Mouse", 120.0, "Tech"),
        Produto("Caneta", 5.0, "Papelaria"),
        Produto("Monitor", 1800.0, "Tech")
    )

    // Filtrar, transformar e ordenar
    val techBaratos = produtos
        .filter { it.categoria == "Tech" }
        .filter { it.preco < 2000 }
        .sortedBy { it.preco }
        .map { "${it.nome}: R$ ${it.preco}" }

    techBaratos.forEach { println(it) }
    // Mouse: R$ 120.0
    // Monitor: R$ 1800.0
}

Operações úteis

fun main() {
    val numeros = listOf(3, 1, 4, 1, 5, 9, 2, 6)

    println(numeros.sum())          // 31
    println(numeros.average())      // 3.875
    println(numeros.max())          // 9
    println(numeros.distinct())     // [3, 1, 4, 5, 9, 2, 6]
    println(numeros.take(3))        // [3, 1, 4]
    println(numeros.any { it > 5 }) // true
    println(numeros.all { it > 0 }) // true
}

Agrupando e associando

data class Aluno(val nome: String, val turma: String)

fun main() {
    val alunos = listOf(
        Aluno("Fernanda", "A"), Aluno("Lucas", "B"),
        Aluno("Mariana", "A"), Aluno("Pedro", "B")
    )

    val porTurma = alunos.groupBy { it.turma }
    porTurma.forEach { (turma, lista) ->
        println("Turma $turma: ${lista.map { it.nome }}")
    }
    // Turma A: [Fernanda, Mariana]
    // Turma B: [Lucas, Pedro]
}

Collections em Kotlin são expressivas e poderosas. Com as operações funcionais, você resolve em poucas linhas o que em outras linguagens exigiria loops verbosos.

Transformações avançadas com flatMap e zip

Além das operações básicas, Kotlin oferece funções poderosas para cenários mais elaborados:

data class Pedido(val cliente: String, val itens: List<String>)

fun main() {
    val pedidos = listOf(
        Pedido("Ana", listOf("Pizza", "Suco")),
        Pedido("Bruno", listOf("Hambúrguer", "Batata", "Refrigerante"))
    )

    // flatMap achata listas aninhadas
    val todosItens = pedidos.flatMap { it.itens }
    println(todosItens) // [Pizza, Suco, Hambúrguer, Batata, Refrigerante]

    // zip combina duas listas em pares
    val funcionários = listOf("Carlos", "Diana", "Eduardo")
    val cargos = listOf("Dev", "QA", "DevOps")
    val equipe = funcionários.zip(cargos) { nome, cargo -> "$nome ($cargo)" }
    println(equipe) // [Carlos (Dev), Diana (QA), Eduardo (DevOps)]
}

Usando fold e reduce para cálculos

Quando você precisa acumular valores de uma coleção, fold e reduce são essenciais:

data class ItemCarrinho(val nome: String, val preco: Double, val quantidade: Int)

fun main() {
    val carrinho = listOf(
        ItemCarrinho("Camiseta", 79.90, 2),
        ItemCarrinho("Calça", 149.90, 1),
        ItemCarrinho("Tênis", 299.90, 1)
    )

    val total = carrinho.fold(0.0) { acumulador, item ->
        acumulador + (item.preco * item.quantidade)
    }
    println("Total: R$ $total") // Total: R$ 609.6

    // Partition separa em dois grupos
    val (caros, baratos) = carrinho.partition { it.preco > 100 }
    println("Caros: ${caros.map { it.nome }}")   // [Calça, Tênis]
    println("Baratos: ${baratos.map { it.nome }}") // [Camiseta]
}

Casos de Uso no Mundo Real

  • Processamento de respostas de API: ao receber uma lista de objetos JSON, use map para converter em data classes e filter para selecionar apenas os dados relevantes.
  • Relatórios e dashboards: groupBy, sumOf e associate permitem agregar dados para gerar relatórios sem precisar de SQL.
  • Validação de formulários: use all para verificar se todos os campos estão preenchidos ou any para detectar se há algum erro.
  • Sistemas de busca: combine filter, sortedBy e take para implementar buscas com ranking e paginação.
  • Processamento em lote: chunked e windowed dividem listas grandes em blocos menores para processamento paralelo ou em batches.

Boas Práticas

  • Prefira coleções imutáveis: use listOf, setOf e mapOf como padrão. Só use versões mutáveis quando realmente precisar modificar a coleção.
  • Encadeie operações funcionais em vez de usar loops for com variáveis mutáveis. O código fica mais legível e menos propenso a erros.
  • Use Sequence para coleções grandes: quando encadeia muitas operações em coleções com milhares de itens, converta para sequence com .asSequence() para evitar criar listas intermediárias.
  • Evite acessar índices diretamente com get() ou [] sem verificar o tamanho. Prefira getOrNull(), firstOrNull() ou find.
  • Não misture lógica de negócio com transformações: extraia lambdas complexas para funções nomeadas.

Erros Comuns

  • Tentar modificar uma coleção imutável: chamar add() ou remove() em uma List retornada por listOf() causa UnsupportedOperationException. Se precisar modificar, use mutableListOf() ou crie uma nova lista com operações como + ou filter.
  • Confundir map com forEach: map transforma e retorna uma nova lista; forEach apenas executa uma ação para cada item sem retorno. Usar map quando você não precisa do resultado desperdiça memória.
  • Ignorar a diferença entre List e MutableList em parâmetros: se uma função recebe MutableList, quem chama fica preso a esse tipo. Prefira receber List e retornar List.
  • Encadear muitas operações sem asSequence(): cada operação como filter e map cria uma lista intermediária. Para coleções grandes, isso pode causar problemas de performance.

Perguntas Frequentes

Qual a diferença entre List e Array em Kotlin? List é uma interface de coleção com operações funcionais ricas e imutabilidade por padrão. Array é um wrapper sobre arrays do Java, com tamanho fixo. Na maioria dos casos, prefira List.

Quando devo usar Set em vez de List? Use Set quando a unicidade dos elementos é importante e você não precisa de ordem de inserção garantida. Verificar se um elemento existe em um Set é O(1), enquanto em uma List é O(n).

O que é Sequence e quando usar? Sequence processa elementos de forma preguiçosa (lazy), um por um, em vez de criar listas intermediárias a cada operação. Use quando tiver coleções grandes (milhares de itens) com múltiplas transformações encadeadas.

Posso converter entre tipos de coleção? Sim. Use toList(), toSet(), toMap(), toMutableList() e variantes para converter entre qualquer tipo de coleção. Também é possível usar associate e groupBy para criar mapas a partir de listas.