Neste tutorial, vamos explorar em profundidade o sistema de variáveis e tipos de dados do Kotlin. Você vai aprender a diferença entre val e var, conhecer todos os tipos primitivos disponíveis e dominar a inferência de tipos, que é uma das funcionalidades mais poderosas da linguagem.

Declaração de Variáveis: val vs var

Em Kotlin, existem duas formas de declarar variáveis: usando val para valores imutáveis e var para valores mutáveis. Essa distinção é fundamental e afeta diretamente a qualidade e segurança do seu código.

A palavra-chave val (de “value”) cria uma variável que não pode ter seu valor reatribuído após a inicialização. Pense nela como uma constante local. Já var (de “variable”) permite que o valor seja alterado quantas vezes forem necessárias.

fun main() {
    // val - imutável: não pode ser reatribuída
    val nome = "Maria"
    val idade = 28
    val pi = 3.14159

    // var - mutável: pode ser reatribuída
    var contador = 0
    var saldo = 1000.50
    var ativo = true

    // Isso funciona:
    contador = 1
    saldo = 950.75
    ativo = false

    // Isso NÃO compila (val não pode ser reatribuído):
    // nome = "João"  // Erro: Val cannot be reassigned

    println("Nome: $nome, Idade: $idade")
    println("Contador: $contador, Saldo: R$$saldo, Ativo: $ativo")
}

A regra de ouro em Kotlin é: sempre comece com val. Só mude para var quando você realmente precisar modificar o valor. Isso torna o código mais previsível, facilita o raciocínio sobre o programa e reduz bugs relacionados a estado mutável. Em programação funcional e concorrente, a imutabilidade é especialmente valiosa porque elimina uma classe inteira de problemas de sincronização.

É importante entender que val torna a referência imutável, não necessariamente o objeto em si. Por exemplo, uma lista mutável atribuída a um val ainda pode ter seus elementos alterados — o que não pode mudar é a referência para outra lista.

Tipos Primitivos em Kotlin

Kotlin possui um conjunto completo de tipos de dados que cobrem as necessidades mais comuns de programação. Diferentemente de Java, em Kotlin todos os tipos são objetos, mas o compilador otimiza automaticamente os tipos numéricos para primitivos da JVM quando possível.

Tipos Numéricos Inteiros

Kotlin oferece quatro tipos inteiros com diferentes tamanhos, cada um adequado para diferentes cenários:

fun main() {
    // Byte: 8 bits (-128 a 127)
    val pequeno: Byte = 127

    // Short: 16 bits (-32768 a 32767)
    val medio: Short = 32000

    // Int: 32 bits (-2^31 a 2^31 - 1) — padrão para inteiros
    val normal: Int = 2_000_000_000
    val outroInt = 42  // inferido como Int

    // Long: 64 bits (-2^63 a 2^63 - 1)
    val grande: Long = 9_000_000_000_000L
    val outroLong = 100L  // sufixo L força Long

    println("Byte: $pequeno")
    println("Short: $medio")
    println("Int: $normal (outro: $outroInt)")
    println("Long: $grande (outro: $outroLong)")

    // Underscores em números para legibilidade
    val umMilhao = 1_000_000
    val cartaoCredito = 1234_5678_9012_3456L
    val hexadecimal = 0xFF_EC_DE_5E
    val binario = 0b1010_0101

    println("Um milhão: $umMilhao")
    println("Hex: $hexadecimal, Binário: $binario")
}

Observe o uso de underscores nos números literais. Esse recurso melhora significativamente a legibilidade de números grandes sem afetar o valor. O compilador simplesmente ignora os underscores.

Tipos de Ponto Flutuante

Para números decimais, Kotlin disponibiliza dois tipos: Float (32 bits, precisão simples) e Double (64 bits, precisão dupla). O tipo padrão para literais decimais é Double.

fun main() {
    // Double: 64 bits — padrão para decimais
    val preco = 29.99  // inferido como Double
    val temperatura: Double = -3.5

    // Float: 32 bits — precisa do sufixo 'f' ou 'F'
    val taxa: Float = 0.15f
    val percentual = 85.5f

    // Operações com decimais
    val subtotal = 100.0
    val desconto = subtotal * 0.10
    val total = subtotal - desconto

    println("Subtotal: R$$subtotal")
    println("Desconto: R$$desconto")
    println("Total: R$$total")

    // Cuidado com precisão de ponto flutuante
    println("0.1 + 0.2 = ${0.1 + 0.2}")  // 0.30000000000000004
}

Para cálculos financeiros onde a precisão é crítica, considere usar BigDecimal em vez de Double ou Float. Os tipos de ponto flutuante seguem o padrão IEEE 754 e podem apresentar pequenas imprecisões em certas operações.

Tipos Boolean, Char e String

Além dos numéricos, Kotlin possui tipos essenciais para trabalhar com valores lógicos, caracteres e texto.

O tipo Boolean representa valores true ou false e é amplamente utilizado em estruturas condicionais. O tipo Char representa um único caractere Unicode e é delimitado por aspas simples. Já String representa uma sequência de caracteres e é delimitada por aspas duplas.

fun main() {
    // Boolean
    val ativo: Boolean = true
    val maiorDeIdade = false  // inferido como Boolean
    val resultado = 10 > 5   // true

    // Char
    val letra: Char = 'A'
    val digito = '7'
    val emoji = '\u2764'  // caractere Unicode (coração)

    // String
    val saudacao = "Olá, Kotlin!"
    val multilinhas = """
        |Primeira linha
        |Segunda linha
        |Terceira linha
    """.trimMargin()

    // String templates
    val nome = "Ana"
    val idade = 25
    val apresentacao = "Meu nome é $nome e tenho $idade anos."
    val calculo = "Daqui a 5 anos terei ${idade + 5} anos."

    println("Boolean: $ativo, $maiorDeIdade, $resultado")
    println("Char: $letra, $digito, $emoji")
    println(saudacao)
    println(multilinhas)
    println(apresentacao)
    println(calculo)
}

As string templates são um recurso extremamente útil do Kotlin. Elas permitem incorporar variáveis diretamente no texto com $variavel e expressões mais complexas com ${expressao}. Isso elimina a necessidade de concatenação manual com o operador +, tornando o código mais limpo e legível.

Inferência de Tipos e Conversões

O sistema de inferência de tipos do Kotlin é sofisticado e inteligente. O compilador consegue deduzir automaticamente o tipo de uma variável com base no valor atribuído a ela, eliminando a necessidade de declarações explícitas na maioria dos casos.

Quando você escreve val x = 42, o compilador sabe que x é do tipo Int. Quando escreve val nome = "Kotlin", ele sabe que é String. Essa inferência funciona em praticamente todos os contextos, incluindo retornos de funções e expressões complexas.

No entanto, diferente de algumas linguagens, Kotlin não faz conversões implícitas entre tipos numéricos. Você precisa usar funções de conversão explícitas como toInt(), toLong(), toDouble() e assim por diante.

fun main() {
    // Inferência de tipos
    val numero = 42          // Int
    val decimal = 3.14       // Double
    val texto = "Kotlin"     // String
    val flag = true          // Boolean

    // Conversões explícitas (não há conversão implícita!)
    val inteiro: Int = 100
    val longo: Long = inteiro.toLong()    // Int -> Long
    val decimal2: Double = inteiro.toDouble()  // Int -> Double
    val flutuante: Float = decimal2.toFloat()  // Double -> Float
    val byte: Byte = inteiro.toByte()          // Int -> Byte

    // Conversão String -> Número
    val textoNumero = "123"
    val convertido = textoNumero.toInt()
    val seguro = "abc".toIntOrNull()  // retorna null em vez de exceção

    println("Convertido: $convertido")
    println("Seguro: $seguro")  // null

    // Tipo explícito quando necessário
    val valor: Long = 42  // sem o tipo, seria inferido como Int

    // Checagem de tipo com 'is'
    val algo: Any = "Sou uma String"
    if (algo is String) {
        // Smart cast: 'algo' já é tratado como String aqui
        println("Tamanho: ${algo.length}")
    }
}

O recurso de smart cast é uma funcionalidade elegante do Kotlin. Quando você verifica o tipo de uma variável com is, o compilador automaticamente faz o cast dentro do bloco condicional, eliminando a necessidade de casts manuais como em Java. Isso funciona com verificações de null safety também.

Constantes de Compilação com const val

Além de val, Kotlin oferece const val para constantes que são resolvidas em tempo de compilação. Essas constantes devem ser do tipo String ou de um tipo primitivo, e só podem ser declaradas no nível superior do arquivo ou dentro de um companion object.

A diferença entre val e const val é sutil mas importante: val é resolvido em tempo de execução (o valor pode ser calculado), enquanto const val precisa ser um valor literal conhecido em tempo de compilação.

Dicas e Erros Comuns

Ao trabalhar com variáveis e tipos em Kotlin, fique atento aos seguintes pontos que costumam causar confusão em iniciantes:

  1. Usar var quando val bastaria: essa é a armadilha mais comum. Sempre comece com val e só mude para var se realmente precisar reatribuir o valor. IDEs como IntelliJ IDEA avisam quando um var poderia ser val.

  2. Esperar conversão implícita entre tipos numéricos: diferente de Java, val x: Long = 42 não converte automaticamente o Int 42 para Long em todos os contextos. Use 42L ou .toLong() quando necessário.

  3. Confundir imutabilidade da referência com imutabilidade do objeto: val lista = mutableListOf(1, 2, 3) impede reatribuir lista, mas permite modificar o conteúdo com lista.add(4). Para imutabilidade completa, use listOf().

  4. Não usar toIntOrNull() para conversões seguras: se você tentar "abc".toInt(), vai receber uma exceção. Sempre use toIntOrNull() quando a conversão pode falhar, e trate o resultado null com o operador Elvis ?:.

  5. Declarar tipos desnecessariamente: aproveite a inferência de tipos. Escreva val nome = "Kotlin" em vez de val nome: String = "Kotlin". A declaração explícita só é necessária quando o tipo não pode ser inferido ou quando você quer um tipo diferente do inferido.

Conclusão e Próximos Passos

Dominar variáveis e tipos de dados é a base para qualquer programação eficaz em Kotlin. Você aprendeu a diferença crucial entre val e var, conheceu todos os tipos primitivos disponíveis, entendeu a inferência de tipos e as conversões explícitas, e viu como smart casts tornam o código mais seguro e elegante.

Para continuar sua jornada de aprendizado, recomendo os seguintes tutoriais:

Pratique criando pequenos programas que utilizem diferentes tipos de dados e conversões. A familiaridade com o sistema de tipos do Kotlin vai facilitar enormemente seu aprendizado nos tópicos mais avançados.