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:
Usar
varquandovalbastaria: essa é a armadilha mais comum. Sempre comece comvale só mude paravarse realmente precisar reatribuir o valor. IDEs como IntelliJ IDEA avisam quando umvarpoderia serval.Esperar conversão implícita entre tipos numéricos: diferente de Java,
val x: Long = 42não converte automaticamente oInt42 paraLongem todos os contextos. Use42Lou.toLong()quando necessário.Confundir imutabilidade da referência com imutabilidade do objeto:
val lista = mutableListOf(1, 2, 3)impede reatribuirlista, mas permite modificar o conteúdo comlista.add(4). Para imutabilidade completa, uselistOf().Não usar
toIntOrNull()para conversões seguras: se você tentar"abc".toInt(), vai receber uma exceção. Sempre usetoIntOrNull()quando a conversão pode falhar, e trate o resultado null com o operador Elvis?:.Declarar tipos desnecessariamente: aproveite a inferência de tipos. Escreva
val nome = "Kotlin"em vez deval 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:
- Estruturas Condicionais — aprenda a usar
if,whene controlar o fluxo do programa - Funções em Kotlin — descubra como criar funções com parâmetros tipados
- Null Safety — domine o sistema de segurança contra nulos do Kotlin
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.