O que é abstract em Kotlin?
O modificador abstract em Kotlin marca classes e membros que não possuem implementação completa e precisam ser implementados por subclasses. Uma classe abstrata não pode ser instanciada diretamente — ela serve como base para outras classes.
Diferente de interfaces, classes abstratas podem ter construtores, estado interno e métodos concretos junto com os abstratos.
Pense em uma classe abstrata como uma planta de uma casa: ela define a estrutura geral (quantos quartos, onde fica a cozinha), mas cada construtor vai decidir os detalhes finais (tipo de piso, cor das paredes). Voce não constroi a planta em si, mas toda casa concreta segue aquela planta como base.
Sintaxe básica
abstract class Forma(val cor: String) {
abstract fun area(): Double
abstract fun perimetro(): Double
fun descricao(): String {
return "Forma $cor com area ${area()}"
}
}
class Circulo(cor: String, val raio: Double) : Forma(cor) {
override fun area() = Math.PI * raio * raio
override fun perimetro() = 2 * Math.PI * raio
}
class Retangulo(cor: String, val largura: Double, val altura: Double) : Forma(cor) {
override fun area() = largura * altura
override fun perimetro() = 2 * (largura + altura)
}
fun main() {
val circulo = Circulo("vermelho", 5.0)
println(circulo.descricao()) // Forma vermelho com area 78.53...
val retangulo = Retangulo("azul", 4.0, 6.0)
println(retangulo.descricao()) // Forma azul com area 24.0
}
Propriedades abstratas
Alem de funções, propriedades também podem ser abstratas:
abstract class Veiculo {
abstract val velocidadeMaxima: Int
abstract val tipo: String
fun info() = "$tipo - Velocidade max: $velocidadeMaxima km/h"
}
class Carro : Veiculo() {
override val velocidadeMaxima = 200
override val tipo = "Carro"
}
class Bicicleta : Veiculo() {
override val velocidadeMaxima = 40
override val tipo = "Bicicleta"
}
Classes abstratas não precisam de open
Classes abstratas já sao implicitamente open, entao subclasses podem herda-las sem precisar marcar nada extra. Membros abstratos também sao automaticamente open.
Abstract class vs Interface
| Aspecto | Abstract Class | Interface |
|---|---|---|
| Construtor | Sim | Nao |
| Estado (campos) | Sim | Nao (só propriedades) |
| Heranca multipla | Nao | Sim |
| Metodos concretos | Sim | Sim |
Use classes abstratas quando suas subclasses compartilham estado e comportamento base. Para contratos puros sem estado, prefira interfaces.
Sistema de notificacoes com classe abstrata
Um cenário real e construir um sistema de notificacoes onde cada tipo de notificação compartilha lógica comum mas tem seu próprio método de envio:
abstract class Notificacao(val destinatario: String) {
val criadoEm: Long = System.currentTimeMillis()
abstract fun enviar(mensagem: String): Boolean
abstract fun validarDestinatario(): Boolean
fun enviarComValidacao(mensagem: String): Boolean {
if (!validarDestinatario()) {
println("Destinatario invalido: $destinatario")
return false
}
return enviar(mensagem)
}
}
class NotificacaoEmail(destinatario: String) : Notificacao(destinatario) {
override fun enviar(mensagem: String): Boolean {
println("Enviando email para $destinatario: $mensagem")
return true
}
override fun validarDestinatario(): Boolean {
return destinatario.contains("@") && destinatario.contains(".")
}
}
class NotificacaoSms(destinatario: String) : Notificacao(destinatario) {
override fun enviar(mensagem: String): Boolean {
println("Enviando SMS para $destinatario: $mensagem")
return true
}
override fun validarDestinatario(): Boolean {
return destinatario.length >= 10 && destinatario.all { it.isDigit() || it == '+' }
}
}
Repositorio generico com classe abstrata
Outro uso comum e criar uma camada de acesso a dados reutilizavel:
abstract class Repositorio<T> {
protected val itens = mutableListOf<T>()
abstract fun buscarPorId(id: String): T?
fun salvar(item: T) {
itens.add(item)
println("Item salvo. Total: ${itens.size}")
}
fun listarTodos(): List<T> = itens.toList()
fun removerTodos() {
itens.clear()
}
}
data class Usuario(val id: String, val nome: String, val email: String)
class UsuarioRepositorio : Repositorio<Usuario>() {
override fun buscarPorId(id: String): Usuario? {
return itens.find { it.id == id }
}
}
Casos de Uso no Mundo Real
- Frameworks de UI: classes abstratas definem o ciclo de vida de componentes visuais (como
ActivityeFragmentno Android), onde cada tela implementa seus próprios métodos mas herda comportamento padrão. - Camadas de persistencia: repositórios abstratos padronizam operações de CRUD, enquanto implementacoes concretas se conectam a bancos de dados específicos como Room ou Exposed.
- Processamento de dados: pipelines de ETL usam classes abstratas para definir etapas comuns (ler, transformar, gravar), enquanto cada fonte de dados implementa a lógica específica.
- Testes: classes abstratas de teste definem cenários comuns que sao reutilizados por testes concretos de diferentes implementacoes.
Boas Praticas
- Use classes abstratas quando subclasses compartilham estado (propriedades com valor) ou lógica de inicialização via construtor.
- Prefira interfaces quando você precisa apenas de um contrato sem estado.
- Mantenha classes abstratas focadas em um único proposito. Evite criar “super classes” que tentam fazer tudo.
- Combine classes abstratas com generics para criar bases reutilizaveis e type-safe.
- Documente claramente o que cada método abstrato deve fazer, para que quem implementa a subclasse saiba o que e esperado.
Erros Comuns
- Tentar instanciar uma classe abstrata diretamente:
val forma = Forma("azul")causa erro de compilação. Voce deve criar uma subclasse concreta e instancia-la. - Esquecer de implementar todos os membros abstratos: se a subclasse não e abstrata, ela precisa implementar todos os métodos e propriedades abstratos. O compilador aponta o erro, mas pode confundir iniciantes.
- Usar abstract quando interface seria suficiente: se a classe base não tem estado nem construtor, uma interface e mais flexivel porque permite heranca multipla.
- Confundir
abstractcomopen:openpermite que uma classe seja herdada mas fornece implementação completa.abstractexige que subclasses fornecam a implementação.
Perguntas Frequentes
Posso ter um construtor em uma classe abstrata?
Sim. Diferente de interfaces, classes abstratas suportam construtores com parametros. As subclasses devem chamar o construtor da classe abstrata ao herda-la, como em class Circulo(cor: String) : Forma(cor).
Uma classe abstrata pode implementar uma interface? Sim. Uma classe abstrata pode implementar uma ou mais interfaces e até deixar alguns métodos da interface sem implementação para que as subclasses completem.
Posso ter métodos concretos em uma classe abstrata? Sim, e esse e um dos grandes diferenciais. Voce pode misturar métodos abstratos (sem corpo) com métodos concretos (com implementação), permitindo que subclasses reutilizem lógica comum.
Quando devo usar sealed class em vez de abstract class? Use sealed class quando você quer restringir quais subclasses podem existir. Com abstract class, qualquer modulo pode criar uma subclasse. Com sealed class, todas as subclasses devem estar no mesmo pacote, o que permite uso seguro com when.