Neste tutorial, vamos explorar em profundidade o sistema de classes e objetos do Kotlin. Programação orientada a objetos (OOP) é um dos paradigmas mais utilizados no desenvolvimento de software, e o Kotlin oferece uma implementação moderna e concisa desses conceitos. Você aprenderá a declarar classes, usar construtores primários e secundários, definir properties com getters e setters personalizados, trabalhar com init blocks, companion objects e modificadores de visibilidade.
Declaração de Classes
Em Kotlin, classes são declaradas com a palavra-chave class. A forma mais simples de criar uma classe não exige nenhum corpo — apenas o nome é suficiente. Quando a classe possui propriedades e métodos, o corpo é delimitado por chaves.
// Classe vazia (válida em Kotlin)
class Vazio
// Classe com propriedades e métodos
class Pessoa {
var nome: String = ""
var idade: Int = 0
fun apresentar(): String {
return "Olá, meu nome é $nome e tenho $idade anos."
}
}
fun main() {
val pessoa = Pessoa()
pessoa.nome = "Mariana"
pessoa.idade = 28
println(pessoa.apresentar())
// Olá, meu nome é Mariana e tenho 28 anos.
}
Observe que em Kotlin não usamos a palavra-chave new para criar instâncias. Basta chamar o nome da classe como uma função. Isso torna a criação de objetos mais limpa e natural.
Construtor Primário
O construtor primário faz parte da declaração da classe e é colocado logo após o nome. Parâmetros precedidos por val ou var automaticamente se tornam propriedades da classe, eliminando a necessidade de declaração manual e atribuição no corpo.
class Usuario(
val nome: String,
val email: String,
var ativo: Boolean = true
) {
fun resumo() = "Usuário: $nome ($email) - Ativo: $ativo"
}
fun main() {
val user1 = Usuario("Carlos", "carlos@email.com")
val user2 = Usuario("Ana", "ana@email.com", false)
println(user1.resumo()) // Usuário: Carlos (carlos@email.com) - Ativo: true
println(user2.resumo()) // Usuário: Ana (ana@email.com) - Ativo: false
user1.ativo = false // 'var' permite reatribuição
// user1.nome = "Outro" // ERRO: 'val' é imutável
}
Essa é uma das grandes vantagens do Kotlin sobre o Java: o construtor primário com declaração de propriedades reduz drasticamente o boilerplate. O que em Java exigiria campos privados, construtor e getters/setters, em Kotlin se resolve em poucas linhas.
Construtor Secundário
Construtores secundários são declarados dentro do corpo da classe usando a palavra-chave constructor. Eles devem delegar para o construtor primário, direta ou indiretamente, usando this().
class Produto(val nome: String, val preco: Double) {
var categoria: String = "Geral"
var estoque: Int = 0
// Construtor secundário que delega para o primário
constructor(nome: String, preco: Double, categoria: String) : this(nome, preco) {
this.categoria = categoria
}
constructor(nome: String, preco: Double, categoria: String, estoque: Int) : this(nome, preco, categoria) {
this.estoque = estoque
}
fun detalhes() = "$nome | R$${"%.2f".format(preco)} | $categoria | Estoque: $estoque"
}
fun main() {
val p1 = Produto("Teclado", 199.90)
val p2 = Produto("Mouse", 89.90, "Periféricos")
val p3 = Produto("Monitor", 1299.00, "Monitores", 15)
println(p1.detalhes()) // Teclado | R$199.90 | Geral | Estoque: 0
println(p2.detalhes()) // Mouse | R$89.90 | Periféricos | Estoque: 0
println(p3.detalhes()) // Monitor | R$1299.00 | Monitores | Estoque: 15
}
Na prática, construtores secundários são menos comuns em Kotlin do que em Java, porque default parameters no construtor primário resolvem a maioria dos casos de sobrecarga.
Properties: Getters e Setters Personalizados
Em Kotlin, toda propriedade tem um getter implícito (e um setter, se for var). Você pode personalizar esses acessores para adicionar lógica de validação ou transformação.
class ContaBancaria(val titular: String, saldoInicial: Double) {
var saldo: Double = saldoInicial
private set // setter é privado: só a própria classe altera
val estaPositiva: Boolean
get() = saldo > 0 // getter personalizado (calculado a cada acesso)
var email: String = ""
set(value) {
if (value.contains("@")) {
field = value.lowercase()
} else {
println("Email inválido: $value")
}
}
fun depositar(valor: Double) {
if (valor > 0) saldo += valor
}
fun sacar(valor: Double): Boolean {
return if (valor in 0.01..saldo) {
saldo -= valor
true
} else {
false
}
}
}
fun main() {
val conta = ContaBancaria("João Silva", 1000.0)
conta.depositar(500.0)
println("Saldo: R$${conta.saldo}") // Saldo: R$1500.0
println("Positiva: ${conta.estaPositiva}") // Positiva: true
conta.email = "JOAO@Email.COM"
println("Email: ${conta.email}") // Email: joao@email.com
conta.email = "invalido" // Email inválido: invalido
}
A palavra-chave field dentro do setter é o backing field — a referência real ao valor armazenado. Sem ela, atribuir diretamente à propriedade dentro do seu próprio setter causaria uma recursão infinita.
Init Blocks
O bloco init é executado imediatamente após o construtor primário, na ordem em que aparece no corpo da classe. Você pode ter múltiplos blocos init, e eles são úteis para validações e lógica de inicialização.
class Pedido(val cliente: String, val itens: List<String>) {
val totalItens: Int
val status: String
init {
require(cliente.isNotBlank()) { "Nome do cliente não pode ser vazio" }
require(itens.isNotEmpty()) { "Pedido deve ter pelo menos um item" }
totalItens = itens.size
}
init {
status = if (totalItens > 5) "Grande" else "Normal"
println("Pedido criado para $cliente com $totalItens itens [$status]")
}
fun resumo() = "Pedido de $cliente: ${itens.joinToString(", ")} ($status)"
}
fun main() {
val pedido = Pedido("Maria", listOf("Arroz", "Feijão", "Café"))
println(pedido.resumo())
// Pedido de Maria: Arroz, Feijão, Café (Normal)
// Isso lançaria IllegalArgumentException:
// val pedidoInvalido = Pedido("", listOf("Item"))
}
Blocos init são executados como parte do construtor primário, intercalados com as inicializações de propriedades na ordem em que aparecem no código-fonte.
Companion Objects
Em Kotlin não existem membros estáticos como em Java. A alternativa é o companion object, um objeto singleton associado à classe que pode conter propriedades e funções acessíveis pelo nome da classe.
class Configuracao private constructor(val ambiente: String, val debug: Boolean) {
companion object {
private const val VERSAO = "1.0.0"
fun desenvolvimento() = Configuracao("dev", true)
fun producao() = Configuracao("prod", false)
fun versao() = VERSAO
}
fun info() = "Ambiente: $ambiente | Debug: $debug | Versão: ${versao()}"
}
fun main() {
val configDev = Configuracao.desenvolvimento()
val configProd = Configuracao.producao()
println(configDev.info()) // Ambiente: dev | Debug: true | Versão: 1.0.0
println(configProd.info()) // Ambiente: prod | Debug: false | Versão: 1.0.0
println(Configuracao.versao()) // 1.0.0
}
Companion objects são frequentemente usados para implementar o padrão Factory Method, como no exemplo acima, onde o construtor é privado e as instâncias são criadas por meio de funções descritivas.
Modificadores de Visibilidade
Kotlin oferece quatro modificadores de visibilidade que controlam o acesso a classes, funções e propriedades:
class ExemploVisibilidade {
public val publico = "Acessível de qualquer lugar" // padrão
private val privado = "Só dentro desta classe"
protected val protegido = "Nesta classe e subclasses"
internal val interno = "Dentro do mesmo módulo"
private fun metodoPrivado() = "Lógica interna"
fun metodoPublico(): String {
// Pode acessar todos os membros
return "$publico | ${metodoPrivado()}"
}
}
// Top-level: 'protected' não é permitido
private fun funcaoPrivadaDoArquivo() = "Visível apenas neste arquivo"
internal fun funcaoInterna() = "Visível no módulo"
Em Kotlin, o padrão é public, diferente de Java onde o padrão é package-private. O modificador internal é único do Kotlin e restringe o acesso ao módulo de compilação, sendo muito útil para bibliotecas que querem esconder detalhes de implementação.
Erros Comuns
Esquecer val ou var no construtor primário. Parâmetros do construtor sem val/var não se tornam propriedades da classe. Eles só existem durante a execução do construtor e dos blocos init.
// ERRADO: 'nome' não é propriedade, não pode ser acessado depois
// class Errada(nome: String) { fun getNome() = nome } // Erro
// CORRETO
class Correta(val nome: String)
Recursão infinita no setter. Usar o nome da propriedade dentro do seu próprio setter em vez de field causa um loop infinito que resulta em StackOverflowError.
Confundir companion object com instância. Membros do companion object pertencem à classe, não a instâncias individuais. Tentar acessar propriedades de instância dentro do companion object causa erro de compilação.
Ignorar init order. Propriedades e blocos init são executados na ordem em que aparecem. Referenciar uma propriedade antes de sua declaração pode resultar em valores inesperados ou nulos.
Conclusão e Próximos Passos
Neste tutorial, você dominou os fundamentos de classes e objetos em Kotlin: declaração de classes, construtores primários e secundários, properties com getters e setters customizados, blocos init, companion objects e modificadores de visibilidade. Esses conceitos formam a base da programação orientada a objetos em Kotlin.
O próximo passo é aprender sobre herança em Kotlin, onde você verá como criar hierarquias de classes usando open, override e classes abstratas. Também recomendamos estudar data classes, que geram automaticamente equals(), hashCode(), toString() e copy(). Com o domínio de classes e objetos, você está pronto para construir aplicações Kotlin bem estruturadas e de fácil manutenção.