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
mappara converter em data classes efilterpara selecionar apenas os dados relevantes. - Relatórios e dashboards:
groupBy,sumOfeassociatepermitem agregar dados para gerar relatórios sem precisar de SQL. - Validação de formulários: use
allpara verificar se todos os campos estão preenchidos ouanypara detectar se há algum erro. - Sistemas de busca: combine
filter,sortedByetakepara implementar buscas com ranking e paginação. - Processamento em lote:
chunkedewindoweddividem listas grandes em blocos menores para processamento paralelo ou em batches.
Boas Práticas
- Prefira coleções imutáveis: use
listOf,setOfemapOfcomo padrão. Só use versões mutáveis quando realmente precisar modificar a coleção. - Encadeie operações funcionais em vez de usar loops
forcom variáveis mutáveis. O código fica mais legível e menos propenso a erros. - Use
Sequencepara coleções grandes: quando encadeia muitas operações em coleções com milhares de itens, converta parasequencecom.asSequence()para evitar criar listas intermediárias. - Evite acessar índices diretamente com
get()ou[]sem verificar o tamanho. PrefiragetOrNull(),firstOrNull()oufind. - 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()ouremove()em umaListretornada porlistOf()causaUnsupportedOperationException. Se precisar modificar, usemutableListOf()ou crie uma nova lista com operações como+oufilter. - Confundir
mapcomforEach:maptransforma e retorna uma nova lista;forEachapenas executa uma ação para cada item sem retorno. Usarmapquando você não precisa do resultado desperdiça memória. - Ignorar a diferença entre
ListeMutableListem parâmetros: se uma função recebeMutableList, quem chama fica preso a esse tipo. Prefira receberListe retornarList. - Encadear muitas operações sem
asSequence(): cada operação comofilteremapcria 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.