O que é val em Kotlin?

A palavra-chave val em Kotlin serve para declarar variáveis somente leitura, ou seja, uma vez que você atribui um valor a ela, não dá pra mudar depois. É como se fosse uma constante local — você define o valor e pronto, ele fica fixo dali em diante.

Se você já programou em Java, pode pensar no val como o equivalente ao final. A diferença é que em Kotlin, usar val é muito mais natural e incentivado pela própria linguagem.

Por que usar val?

A grande sacada de usar val é tornar o código mais previsível e seguro. Quando você garante que um valor não vai mudar, fica muito mais fácil de entender o que tá acontecendo no programa. Menos bugs, menos dor de cabeça.

Na prática, a recomendação da comunidade Kotlin é: sempre comece com val. Só troque pra var se realmente precisar alterar o valor depois.

Exemplo prático

fun main() {
    val nome = "Kotlin Brasil"
    val ano = 2026

    println("Bem-vindo ao $nome! Estamos em $ano.")

    // Isso aqui daria erro de compilacao:
    // nome = "Outro nome"  // Val cannot be reassigned
}

Perceba que o compilador já infere o tipo automaticamente. O nome é uma String e o ano é um Int, sem precisar declarar isso explicitamente.

val não significa imutável em tudo

Um ponto importante: val garante que a referência não muda, mas o conteúdo do objeto pode mudar. Por exemplo, se você declara uma lista mutável com val, não pode reatribuir a variável, mas pode adicionar itens na lista.

val frutas = mutableListOf("Manga", "Acerola")
frutas.add("Goiaba") // Funciona!
// frutas = mutableListOf("Banana") // Erro! Não pode reatribuir

Essa distinção é fundamental pra quem tá começando com Kotlin. Usar val sempre que possível é uma prática que vai deixar seu código mais limpo e confiável.

val com tipos explícitos e inicialização tardia

Em algumas situações, você pode querer declarar o tipo explicitamente ou inicializar o val em momentos diferentes dentro do mesmo bloco.

fun calcularDesconto(preco: Double, porcentagem: Int): String {
    val desconto: Double
    val mensagem: String

    if (porcentagem > 0) {
        desconto = preco * porcentagem / 100
        mensagem = "Desconto de R$ ${"%.2f".format(desconto)}"
    } else {
        desconto = 0.0
        mensagem = "Sem desconto aplicado"
    }

    return mensagem
}

fun main() {
    println(calcularDesconto(150.0, 10)) // Desconto de R$ 15,00
    println(calcularDesconto(80.0, 0))   // Sem desconto aplicado
}

O compilador do Kotlin permite que um val seja atribuído em branches diferentes de um if ou when, desde que ele seja atribuído exatamente uma vez em todos os caminhos possíveis.

val em data classes e propriedades

O val é amplamente usado em data class para criar objetos imutáveis, um padrão muito valorizado em programação funcional e em aplicações concorrentes.

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

fun main() {
    val notebook = Produto("Notebook", 3500.0, "Eletrônicos")
    println(notebook) // Produto(nome=Notebook, preco=3500.0, categoria=Eletrônicos)

    // Para "alterar", crie uma cópia com copy()
    val comDesconto = notebook.copy(preco = 2990.0)
    println(comDesconto) // Produto(nome=Notebook, preco=2990.0, categoria=Eletrônicos)
}

Casos de Uso no Mundo Real

  • Configurações de aplicação: Valores como URL base da API, chaves de configuração e timeouts são naturalmente imutáveis. Declará-los com val garante que nenhuma parte do código vai alterar esses valores acidentalmente durante a execução.
  • Resultados de consultas ao banco: Quando você busca dados do banco ou de uma API, o resultado retornado não deve ser modificado. Usar val para armazenar esses resultados evita alterações involuntárias que poderiam causar inconsistências.
  • Injeção de dependências: Em frameworks como Koin e Dagger/Hilt, as dependências injetadas são declaradas com val. Uma vez injetado, o serviço ou repositório não deve ser substituído durante o ciclo de vida do componente.
  • Parâmetros de funções em Kotlin: Todos os parâmetros de funções em Kotlin são implicitamente val. Você não pode reatribuí-los dentro do corpo da função, o que é uma decisão de design da linguagem que evita efeitos colaterais inesperados.

Boas Práticas

  • Comece sempre com val: Essa é a regra número um. Declare tudo como val primeiro. Só mude para var se o compilador indicar que você precisa reatribuir o valor. Essa abordagem é chamada de “immutability by default”.
  • Combine val com coleções imutáveis: Use listOf(), mapOf() e setOf() junto com val para garantir imutabilidade total. Usar val com mutableListOf() protege apenas a referência, não o conteúdo.
  • Use val em propriedades de classe: Propriedades declaradas com val em classes são thread-safe para leitura, pois não podem ser reatribuídas após a construção do objeto.
  • Prefira val com expressões when e if: Em Kotlin, if e when são expressões que retornam valor. Use val resultado = when { ... } em vez de declarar um var e atribuir dentro de cada branch.
  • Considere const val para constantes de compilação: Quando o valor é uma primitiva ou String conhecida em tempo de compilação, use const val no companion object para otimizar a performance.

Erros Comuns

  • Achar que val torna o objeto completamente imutável: Como mostrado acima, val protege apenas a referência. Se o objeto apontado é mutável (como MutableList), seu conteúdo interno ainda pode ser alterado. Para imutabilidade total, combine val com tipos imutáveis.
  • Usar var por hábito de outras linguagens: Quem vem de Java, JavaScript ou Python tende a usar var para tudo. Em Kotlin, isso desperdiça uma das maiores vantagens da linguagem. O IntelliJ IDEA inclusive mostra um aviso quando um var nunca é reatribuído.
  • Confundir val com const val: val é avaliado em tempo de execução, enquanto const val é avaliado em tempo de compilação. const val só pode ser usado com tipos primitivos e String em objetos ou companion objects.
  • Não usar val em loops for: A variável de iteração em um for já é implicitamente val. Tentar reatribuí-la dentro do loop causa erro de compilação, o que é o comportamento correto e esperado.
  • Declarar val sem inicializar fora de condicionais: Um val deve ser inicializado antes de ser usado. Se você declará-lo sem valor e esquecer de atribuir em algum branch do código, o compilador vai apontar o erro.

Perguntas Frequentes

Qual a diferença entre val e const val? val pode receber qualquer valor em tempo de execução, incluindo resultados de funções e objetos complexos. const val é restrito a tipos primitivos e String, precisa ser declarado em companion objects ou top-level, e o valor é embutido diretamente no bytecode em tempo de compilação.

val é a mesma coisa que final em Java? Conceitualmente sim, ambos impedem a reatribuição da variável. A diferença é que em Kotlin, val é a forma padrão e incentivada de declarar variáveis, enquanto final em Java é uma anotação adicional que poucos desenvolvedores usam consistentemente.

Posso usar val com lateinit? Não. lateinit só pode ser usado com var, pois a variável precisa ser atribuída depois da declaração, o que contradiz a natureza somente-leitura do val. Para inicialização tardia com val, use by lazy.

val torna meu código mais lento? Não. Na verdade, o compilador pode fazer otimizações mais agressivas quando sabe que um valor não vai mudar. Além disso, val não gera overhead de runtime — no bytecode, a diferença é simplesmente a ausência de um método setter.

Termos Relacionados

  • var — A contraparte mutável de val, usada quando o valor precisa ser reatribuído.
  • Data Class — Classes de dados que usam val para criar objetos imutáveis.
  • Null Safety — Sistema de tipos nulos do Kotlin, frequentemente combinado com val para variáveis seguras.
  • when — Expressão que retorna valor e combina perfeitamente com val para atribuição condicional.