Kotlin: Variáveis e Sistema de Tipos do Kotlin em…
Última atualização: 2026-08-26
O sistema de tipos do Kotlin não é uma cópia do Java — ele elimina conversões forçadas com smart casts, constrói uma álgebra de tipos completa com Any/Unit/Nothing e faz o sistema de tipos trabalhar a seu favor, não contra você.
1. O que Você Aprenderá
- Tipos básicos:
Int/Double/Boolean/Char/String - Smart Cast: usar diretamente após verificação
is - Conversões explícitas:
toInt()/toDouble(), sem estreitamento implícito Any/Unit/Nothing— três tipos de topo- Apelidos de tipo
typealiaspara melhor legibilidade
2. A História Real de um Desenvolvedor
(1) Problema: O Inferno das Conversões de Tipo do Java
Bob estava mantendo código Java do OrderProcessor, e conversões forçadas estavam por toda parte: (Order), (String). Faltar uma única verificação instanceof provocava uma ClassCastException — 3-5 incidentes em produção por trimestre.
(2) Solução: Smart Cast do Kotlin
// Java: verboso e propenso a erros
if (obj instanceof Order) {
Order order = (Order) obj; // Precisa converter novamente
System.out.println(order.getId());
}
// Kotlin: smart cast elimina a conversão
if (obj is Order) {
println(obj.id) // Nenhuma conversão necessária!
}
O compilador Kotlin insere automaticamente conversões seguras após verificações
is. Bob nunca mais escreveu uma conversão manual.
3. Tipos Básicos
(1) Tipos Numéricos
O Kotlin não expõe os tipos primitivos do Java (int/double) — tudo é objeto — mas o compilador os otimiza para primitivos na JVM.
val intValue: Int = 42 // 32 bits
val longValue: Long = 42L // 64 bits
val doubleValue: Double = 3.14 // 64 bits IEEE 754
val floatValue: Float = 3.14f // 32 bits IEEE 754
// Sublinhado para legibilidade
val million = 1_000_000
val creditCard = 1234_5678_9012_3456L
val bytes = 0b11010010_01101001
| Tipo | Bits | Intervalo | Equivalente Java |
|---|---|---|---|
Byte |
8 | -128 ~ 127 | byte |
Short |
16 | -32768 ~ 32767 | short |
Int |
32 | -2³¹ ~ 2³¹-1 | int |
Long |
64 | -2⁶³ ~ 2⁶³-1 | long |
Float |
32 | IEEE 754 | float |
Double |
64 | IEEE 754 | double |
(2) Tipos Não Numéricos
val bool: Boolean = true
val char: Char = 'A'
val str: String = "OrderProcessor"
// Caractere NÃO é um número no Kotlin
// val code: Int = 'A' // ERRO
val code: Int = 'A'.code // OK: 65
val charFromCode: Char = 65.toChar() // OK: 'A'
4. Smart Cast
O compilador Kotlin converte automaticamente uma variável para um tipo mais específico após uma verificação de tipo — sem conversão manual necessária.
(1) Smart Cast Básico
fun process(input: Any) {
// Após verificação 'is', o compilador faz smart cast
if (input is String) {
println(input.length) // Smart cast para String
}
// Após verificação '!is'
if (input !is String) return
println(input.length) // Smart cast para String
}
// Smart cast com when
fun describe(obj: Any): String = when (obj) {
is String -> "String: ${obj.length} caracteres" // Smart cast
is Int -> "Int: ${obj.dec()}" // Smart cast
is List<*> -> "Lista: ${obj.size} itens" // Smart cast
else -> "Desconhecido"
}
(2) Limitações do Smart Cast
// AVISO: smart cast pode não funcionar com var ou getters personalizados
var obj: Any = "Hello"
if (obj is String) {
// obj pode ser alterado entre a verificação e o uso
// println(obj.length) // AVISO em alguns casos
}
// Abordagem segura: usar val local
val safeObj = obj
if (safeObj is String) {
println(safeObj.length) // OK - garantido estável
}
(3) Conversão Java vs Smart Cast Kotlin
| Dimensão | Java | Kotlin |
|---|---|---|
| Sintaxe | (Type)obj |
Automático |
| Segurança | Pode causar CCE em tempo de execução | Garantia em tempo de compilação |
| Tamanho do código | Verificação + conversão (dois passos) | Verificação = conversão (um passo) |
| Segurança nula | Pode causar NPE | Lida automaticamente com anuláveis |
5. Conversões Explícitas de Tipos
O Kotlin não suporta conversões de estreitamento implícitas (ex.: Long → Int) — todas as conversões devem ser chamadas explicitamente.
(1) Funções de Conversão
val longVal: Long = 42L
val intVal: Int = longVal.toInt() // Estreitamento explícito
val doubleVal: Double = 3.99
val intFromDouble: Int = doubleVal.toInt() // Trunca: 3
// String para número
val parsed: Int = "42".toInt()
val parsedDouble: Double = "3.14".toDouble()
// Número para string
val strVal: String = 42.toString()
(2) Por Que Não Conversões Implícitas?
// Isso perderia dados silenciosamente - o Kotlin impede
val longValue: Long = 2_147_483_648L // Excede Int.MAX_VALUE
// val intValue: Int = longValue // ERRO DE COMPILAÇÃO
// Deve ser explícito sobre a potencial perda de dados
val intValue: Int = longValue.toInt() // OK, mas pode causar overflow
| Tipo de Conversão | Java | Kotlin |
|---|---|---|
| Alargamento (Int→Long) | Implícito | Explícito toLong() |
| Estreitamento (Long→Int) | Implícito (arriscado) | Explícito toInt() |
| String→Número | Integer.parseInt() |
"42".toInt() |
| Número→String | "" + 42 |
42.toString() |
6. Três Tipos de Topo
(1) Diagrama de Hierarquia de Tipos
classDiagram
Any --> String
Any --> Int
Any --> Order
Any --> Unit
Any --> Nothing
class Any {
+equals()
+hashCode()
+toString()
}
class Unit {
<<singleton>>
}
class Nothing {
<<nunca retorna>>
}
(2) Any: O Pai de Todos os Tipos Não Nulos
// Any é a raiz da hierarquia de tipos do Kotlin (como Object no Java)
val obj: Any = "Hello"
val obj2: Any = 42
val obj3: Any = Order("ORD-001", 299.99, "CONFIRMED")
// Any tem apenas 3 métodos: equals, hashCode, toString
// Use Any? para o tipo raiz anulável
(3) Unit: O "Sem Valor de Retorno" Tipado
// Unit é como void no Java, mas é um tipo real
fun logOrder(order: Order): Unit { // Retorno Unit é opcional
println("Processando ${order.id}")
}
fun logOrder2(order: Order) { // Equivalente - Unit inferido
println("Processando ${order.id}")
}
// Unit é um singleton - pode ser usado como um valor
val unitValue: Unit = Unit
(4) Nothing: Nunca Alcança
// Nothing significa "este código nunca retorna"
fun fail(message: String): Nothing {
throw IllegalStateException(message)
}
// Útil para operador Elvis com exceções
val customer = order.customer ?: fail("Cliente obrigatório")
// Loop infinito também retorna Nothing
fun infiniteLoop(): Nothing {
while (true) { /* nunca retorna */ }
}
(5) Comparação dos Três Tipos de Topo
| Tipo | Significado | Caso de Uso | Equivalente Java |
|---|---|---|---|
Any |
Pai de todos os tipos não nulos | Referências genéricas | Object |
Unit |
Sem valor de retorno significativo | Funções com efeitos colaterais | void |
Nothing |
Nunca alcança o fim | Lançar exceções / loops infinitos | Nenhum |
7. Apelidos de Tipo (typealias)
// Simplificar assinaturas de tipo complexas
typealias OrderMap = Map<String, List<Order>>
typealias OrderProcessor = (List<Order>) -> List<String>
typealias USD = Double
// Uso
val ordersByCustomer: OrderMap = mapOf(
"Alice" to listOf(Order("ORD-001", 299.99, "CONFIRMED"))
)
typealias Predicate<T> = (T) -> Boolean
val isHighValue: Predicate<Order> = { it.total > 10_000 }
// typealias NÃO cria novos tipos - apenas apelidos
val map: OrderMap = ordersByCustomer // Mesmo tipo
8. Exemplo Completo: Processamento Tipado Seguro do OrderProcessor
// ============================================
// OrderProcessor - Processamento de Eventos Tipado Seguro
// Funcionalidade: Processar eventos de pedido com smart cast
// ============================================
typealias OrderId = String
typealias USD = Double
sealed class OrderEvent {
data class Created(val orderId: OrderId, val total: USD) : OrderEvent()
data class Paid(val orderId: OrderId, val amount: USD) : OrderEvent()
data class Shipped(val orderId: OrderId, val trackingCode: String) : OrderEvent()
data class Cancelled(val orderId: OrderId, val reason: String) : OrderEvent()
}
fun handleEvent(event: OrderEvent): String = when (event) {
is OrderEvent.Created -> {
// Smart cast: event.orderId e event.total disponíveis
val threshold = 10_000
val priority = if (event.total > threshold) "VIP" else "STANDARD"
"Pedido ${event.orderId} criado (\$${event.total} USD) -> $priority"
}
is OrderEvent.Paid -> {
val tax = event.amount * 0.08
"Pedido ${event.orderId} pago: \$${event.amount} USD (imposto: \$$tax USD)"
}
is OrderEvent.Shipped ->
"Pedido ${event.orderId} enviado: ${event.trackingCode}"
is OrderEvent.Cancelled ->
"Pedido ${event.orderId} cancelado: ${event.reason}"
}
fun main() {
val events: List<OrderEvent> = listOf(
OrderEvent.Created("ORD-001", 299.99),
OrderEvent.Created("ORD-002", 15_000.00),
OrderEvent.Paid("ORD-001", 299.99),
OrderEvent.Shipped("ORD-001", "TRK-ABC123"),
OrderEvent.Cancelled("ORD-003", "Solicitação do cliente")
)
events.forEach { event ->
val result = handleEvent(event)
println(result)
}
// Verificação de tipo com smart cast
val anyEvent: Any = events[0]
if (anyEvent is OrderEvent.Created) {
println("\nSmart cast funciona: ${anyEvent.orderId} custa \$${anyEvent.total} USD")
}
}
Saída:
Pedido ORD-001 criado ($299.99 USD) -> STANDARD
Pedido ORD-002 criado ($15000.0 USD) -> VIP
Pedido ORD-001 pago: $299.99 USD (imposto: $23.999200000000002 USD)
Pedido ORD-001 enviado: TRK-ABC123
Pedido ORD-003 cancelado: Solicitação do cliente
Smart cast funciona: ORD-001 custa $299.99 USD
9. Exemplos práticos rápidos
▶ Exemplo: val e var com inferência de tipo
// Inferência de tipo: variáveis não precisam de anotação de tipo explícita
val orderId = "ORD-001" // String
val total = 299.99 // Double
val isPaid = true // Boolean
// var com mudança de valor
var status = "PENDING"
status = "CONFIRMED"
// Tipos explícitos quando necessário
val explicitInt: Int = 42
val explicitDouble: Double = 3.14
val explicitString: String = "Olá"
println("orderId: $orderId (${orderId::class.simpleName})")
println("total: $total (${total::class.simpleName})")
println("status: $status")
Saída:
orderId: ORD-001 (String)
total: 299.99 (Double)
status: CONFIRMED
▶ Exemplo: Tipos numéricos básicos
// Tipos inteiros
val byteVal: Byte = 127 // -128 a 127
val shortVal: Short = 32_000 // -32768 a 32767
val intVal: Int = 2_000_000_000 // mais comum
val longVal: Long = 9_000_000_000L // notar sufixo L
// Tipos decimais
val floatVal: Float = 3.14f // notar sufixo f
val doubleVal: Double = 3.14159265358979
// Conversões explícitas
val intFromDouble = doubleVal.toInt() // 3 (truncado)
val doubleFromInt = intVal.toDouble() // 2000000000.0
// Underscores em valores numéricos (legibilidade)
val milhao = 1_000_000
val pi = 3.14159_26535_89793
println("intFromDouble: $intFromDouble")
println("doubleFromInt: $doubleFromInt")
println("milhao: $milhao")
Saída:
intFromDouble: 3
doubleFromInt: 2.0E9
milhao: 1000000
▶ Exemplo: String com métodos comuns
val order = "ORD-001"
// Comprimento, acesso, fatiamento
println("Length: ${order.length}")
println("First 3: ${order.substring(0, 3)}")
println("Contains 001: ${order.contains("001")}")
// Templates e expressões
val total = 299.99
val msg = "Pedido $order: \$$total USD"
println(msg)
// Multilinha com trimMargin
val report = """
|Pedido: $order
|Total: $total
|Status: PENDING
""".trimMargin()
println(report)
// Comparação de strings
val a = "Alice"
val b = "alice"
println("Igual: ${a == b}") // false
println("Igual ignorando caso: ${a.equals(b, ignoreCase = true)}") // true
Saída:
Length: 7
First 3: ORD
Contains 001: true
Pedido ORD-001: $299.99 USD
Pedido: ORD-001
Total: 299.99
Status: PENDING
Igual: false
Igual ignorando caso: true
▶ Exemplo: Any, Unit e Nothing
// Any: tipo raiz - todos os outros tipos herdam dele
fun describe(value: Any): String = "${value::class.simpleName}: $value"
println(describe(42))
println(describe("Olá"))
println(describe(listOf(1, 2, 3)))
// Unit: como void - tipo com valor único ()
fun logMessage(msg: String): Unit {
println("[LOG] $msg")
}
logMessage("Pedido processado")
// Nothing: função "nunca retorna"
fun failWith(message: String): Nothing {
throw IllegalStateException(message)
}
val x: Int = try {
"não é um número".toInt()
} catch (e: NumberFormatException) {
failWith("Entrada inválida: $e")
}
Saída:
Int: 42
String: Olá
List: [1, 2, 3]
[LOG] Pedido processado
Exception: Entrada inválida: ...
▶ Exemplo: Conversões de tipo explícitas
// Sem conversão implícita entre números - deve chamar .toXxx() explicitamente
val intVal: Int = 42
val longVal: Long = intVal.toLong()
val doubleVal: Double = intVal.toDouble()
val stringVal: String = intVal.toString()
// String para número
val parsed = "123".toInt()
val parsedDouble = "3.14".toDouble()
// Tipo para tipo seguro
val maybeInt: Any = "123"
val safeInt = (maybeInt as? Int) ?: 0 // 0 se não for Int
val unsafeInt = maybeInt as Int // lança ClassCastException se falhar
println("long: $longVal")
println("double: $doubleVal")
println("string: $stringVal")
println("parsed: $parsed (${parsed::class.simpleName})")
println("safeInt: $safeInt (já que maybeInt é String)")
Saída:
long: 42
double: 42.0
string: 42
parsed: 123 (Int)
safeInt: 0 (já que maybeInt é String)
▶ Exemplo: typealias para apelidos
// Define apelidos para tipos complexos
typealias OrderId = String
typealias OrderMap = Map<OrderId, Double>
typealias OrderHandler = (OrderId, Double) -> String
// Uso
val prices: OrderMap = mapOf(
"ORD-001" to 299.99,
"ORD-002" to 1500.00
)
// Função que aceita apelido
val formatPrice: OrderHandler = { id, price ->
"$id: \$$price USD"
}
// Chamada
prices.forEach { (id, price) ->
println(formatPrice(id, price))
}
Saída:
ORD-001: $299.99 USD
ORD-002: $1500.0 USD
▶ Exemplo: late e lazy
// late: inicialização tardia mutável
lateinit var config: String
fun initializeConfig() {
config = "produção"
}
fun main() {
initializeConfig()
println("Config: $config")
}
// lazy: inicialização no primeiro acesso
val expensiveValue: String by lazy {
println("Calculando valor caro...")
"resultado-${System.currentTimeMillis()}"
}
println("Antes do acesso")
println("Valor: $expensiveValue") // inicializado aqui
println("Valor: $expensiveValue") // retorna o mesmo valor sem reinicializar
Saída:
Config: produção
Antes do acesso
Calculando valor caro...
Valor: resultado-1700000000000
Valor: resultado-1700000000000
❓ Perguntas Frequentes
P: O
Intdo Kotlin e ointdo Java são a mesma coisa? R: Na JVM, oIntdo Kotlin compila para ointdo Java (tipo primitivo), e faz auto-boxing paraIntegerquando um objeto é necessário. Desenvolvedores não precisam se preocupar com essa diferença.
P: Quando o smart cast não funciona? R: Quando uma variável é
vare pode ser modificada concorrentemente, ou tem um getter personalizado, o compilador não pode garantir tipos consistentes entre a verificação e o uso — smart cast não será aplicado.
P: Qual é o uso prático do tipo
Nothing? R: É usado principalmente para marcar funções que "nunca retornam" (ex.: lançamento de exceções), ajudando o compilador a entender que o código subsequente é inalcançável, o que habilita smart casts e uso do operador Elvis.
P:
typealiascria um novo tipo? R: Não.typealiasé apenas um apelido de tipo — o compilador o substitui pelo tipo original. Para wrappers verdadeiramente seguros de tipo, useinline class/value class.
P: Por que o Kotlin não suporta conversões implícitas de tipos? R: Porque conversões de estreitamento implícitas (ex.:
Long→Int) perdem dados silenciosamente e são uma fonte comum de bugs. Conversões explícitas tornam os desenvolvedores cientes da potencial perda de dados.
P: Qual a diferença entre
AnyeAny?? R:Anyé o pai de todos os tipos não nulos;Any?é o pai de todos os tipos (incluindo anuláveis).Any? = Any | nullé o verdadeiro topo do sistema de tipos do Kotlin.
📖 Resumo
- Os tipos básicos do Kotlin são objetos no código, mas o compilador os otimiza para primitivos na JVM
- Smart cast faz auto-conversão após verificações
is, eliminando conversões forçadas do Java - Todas as conversões de tipo devem ser explícitas (
toInt()/toDouble()), sem estreitamento implícito Anyé a raiz dos tipos não nulos,Unité a versão tipada de void,Nothingsignifica nunca retornatypealiasfornece apelidos legíveis para tipos complexos sem criar novos tipos- Sublinhados numéricos
1_000_000melhoram a legibilidade para números grandes
📝 Exercícios
- Iniciante (⭐): Declare variáveis de diferentes tipos (
Int,Double,Boolean,String) e imprima seus tipos::class. Dica: useval x = 42; println(x::class) - Intermediário (⭐⭐): Escreva uma função que receba
Any, usando smart cast para tratarString(imprimir comprimento) eInt(imprimir valor ao quadrado). Dica:is String/is Int - Desafio (⭐⭐⭐): Use
typealiase sealed classes para projetar um sistema de tipos de eventos de pedido, comwhentratando exaustivamente todos os tipos de evento. Dica: consulte o exemplo completo da Seção 8