O que e Immutable em Kotlin?

Imutabilidade (immutability) e o principio de que um valor, uma vez criado, nao pode ser alterado. Em Kotlin, a imutabilidade e fortemente encorajada e suportada pela linguagem em varios niveis: variaveis com val, colecoes somente leitura, data classes com propriedades val e value classes.

Codigo imutavel e mais facil de entender, testar e paralelizar. Quando um objeto nao muda, voce nao precisa se preocupar com quem mais pode estar modificando ele ao mesmo tempo.

val vs var: o primeiro nivel

A forma mais basica de imutabilidade em Kotlin e usar val em vez de var:

val nome = "Kotlin"    // Imutavel: nao pode ser reatribuido
var idade = 10         // Mutavel: pode ser reatribuido

// nome = "Java"       // Erro de compilacao!
idade = 11             // OK

Porem, val garante apenas que a referencia nao muda. O objeto apontado ainda pode ser mutavel:

val lista = mutableListOf(1, 2, 3)
lista.add(4)  // OK! A referencia nao mudou, mas o conteudo sim
// lista = mutableListOf(5) // Erro! Nao pode reatribuir val

Colecoes somente leitura

Kotlin distingue entre colecoes somente leitura e mutaveis:

// Somente leitura: nao expoe metodos de mutacao
val nomes: List<String> = listOf("Ana", "Bruno", "Carlos")
// nomes.add("Diana")  // Erro de compilacao!

// Mutavel: permite adicionar, remover, alterar
val nomesMutaveis: MutableList<String> = mutableListOf("Ana", "Bruno")
nomesMutaveis.add("Carlos")  // OK

A mesma distincao existe para Set/MutableSet e Map/MutableMap:

val mapaLeitura: Map<String, Int> = mapOf("a" to 1, "b" to 2)
val mapaMutavel: MutableMap<String, Int> = mutableMapOf("a" to 1)
mapaMutavel["c"] = 3  // OK

val conjuntoLeitura: Set<Int> = setOf(1, 2, 3)
val conjuntoMutavel: MutableSet<Int> = mutableSetOf(1, 2)
conjuntoMutavel.add(3)  // OK

Importante: as colecoes “somente leitura” do Kotlin nao sao verdadeiramente imutaveis na JVM. Elas podem ser convertidas com cast para a versao mutavel. Para imutabilidade real, considere bibliotecas como kotlinx.collections.immutable.

Data classes imutaveis

O padrao recomendado e criar data classes com todas as propriedades val:

data class Usuario(
    val id: Long,
    val nome: String,
    val email: String
)

val usuario = Usuario(1, "Ana", "ana@email.com")
// usuario.nome = "Maria"  // Erro! Propriedade val

// Para "modificar", crie uma copia com copy()
val usuarioAtualizado = usuario.copy(nome = "Maria")
println(usuario)            // Usuario(id=1, nome=Ana, email=ana@email.com)
println(usuarioAtualizado)  // Usuario(id=1, nome=Maria, email=maria@email.com)

O metodo copy() cria uma nova instancia com os campos alterados, sem modificar o original. Isso e a essencia da programacao com dados imutaveis.

Imutabilidade profunda vs rasa

Imutabilidade rasa (shallow) significa que a referencia nao muda, mas objetos internos podem:

data class Departamento(val nome: String, val membros: MutableList<String>)

val dept = Departamento("TI", mutableListOf("Ana", "Bruno"))
dept.membros.add("Carlos")  // Compila! A lista interna e mutavel

Para imutabilidade profunda (deep), todos os campos internos tambem devem ser imutaveis:

data class DepartamentoImutavel(val nome: String, val membros: List<String>)

val dept = DepartamentoImutavel("TI", listOf("Ana", "Bruno"))
// dept.membros.add("Carlos")  // Erro! List nao tem add
val deptAtualizado = dept.copy(membros = dept.membros + "Carlos")

Colecoes persistentes com kotlinx.collections.immutable

Para imutabilidade garantida e eficiente, use a biblioteca oficial:

import kotlinx.collections.immutable.*

val lista = persistentListOf(1, 2, 3)
val novaLista = lista.add(4)  // Retorna nova lista, original inalterada

println(lista)      // [1, 2, 3]
println(novaLista)  // [1, 2, 3, 4]

val mapa = persistentMapOf("a" to 1, "b" to 2)
val novoMapa = mapa.put("c", 3)

Colecoes persistentes usam estruturas de dados eficientes (como tries) que compartilham memoria entre versoes, evitando copiar tudo a cada modificacao.

Imutabilidade em concorrencia

A principal vantagem pratica da imutabilidade e em codigo concorrente:

// Seguro para acesso concorrente: dados nunca mudam
data class Configuracao(
    val maxConexoes: Int,
    val timeout: Long,
    val habilitarCache: Boolean
)

val config = Configuracao(10, 5000L, true)

// Pode ser lido por qualquer thread sem sincronizacao
launch(Dispatchers.Default) { usarConfig(config) }
launch(Dispatchers.IO) { usarConfig(config) }

Objetos imutaveis nao precisam de locks, mutex ou qualquer mecanismo de sincronizacao. Eles sao inerentemente thread-safe.

Sealed classes e imutabilidade

Sealed classes combinam bem com imutabilidade para modelar estados:

sealed class EstadoDaTela {
    object Carregando : EstadoDaTela()
    data class Sucesso(val dados: List<String>) : EstadoDaTela()
    data class Erro(val mensagem: String) : EstadoDaTela()
}

// Cada estado e imutavel; transicoes criam novos objetos
var estado: EstadoDaTela = EstadoDaTela.Carregando
estado = EstadoDaTela.Sucesso(listOf("item1", "item2"))

Quando usar imutabilidade

  • Sempre que possivel: a regra geral em Kotlin e preferir val sobre var e List sobre MutableList.
  • Modelos de dados: data classes com propriedades val sao o padrao ouro para representar dados.
  • Estado de UI: em Compose e arquiteturas MVI/MVVM, o estado e imutavel e atualizado por substituicao.
  • Concorrencia: sempre que dados sao compartilhados entre threads ou coroutines.
  • APIs publicas: expor colecoes somente leitura impede que consumidores modifiquem dados internos.

Erros comuns

  1. Assumir que val significa imutavel: val impede reatribuicao da referencia, mas o objeto pode ser mutavel internamente. Uma val lista: MutableList ainda permite modificacoes.

  2. Expor MutableList como retorno: retornar uma MutableList de uma funcao permite que o chamador modifique a colecao interna. Retorne List ou use .toList().

  3. Usar data class com propriedades var: isso quebra o contrato de imutabilidade e causa bugs sutis com hashCode/equals.

  4. Copiar tudo desnecessariamente: em colecoes grandes, criar copias completas a cada modificacao e ineficiente. Use colecoes persistentes.

  5. Esquecer imutabilidade profunda: ter um data class com val mas com campos que sao MutableList internamente.

Termos relacionados

  • val: palavra-chave que declara uma referencia somente leitura (nao reatribuivel).
  • var: palavra-chave que declara uma referencia mutavel.
  • Data Class: classe que gera automaticamente copy(), facilitando a criacao de versoes modificadas de objetos imutaveis.
  • Value Class: tipo inline que encapsula um valor de forma imutavel sem overhead.
  • Sealed Class: hierarquia fechada que modela estados imutaveis de forma segura.
  • Collections: a biblioteca padrao distingue entre colecoes somente leitura e mutaveis.

Imutabilidade e um dos pilares do Kotlin idiomatico. A linguagem facilita a escrita de codigo imutavel em todos os niveis, desde variaveis simples ate estruturas de dados complexas. Adotar imutabilidade como padrao torna o codigo mais previsivel, testavel e seguro em ambientes concorrentes.