O que é abstract em Kotlin?
Resposta rápida: o modificador abstract marca classes e membros incompletos. Uma abstract class não pode ser instanciada; ela serve de base com construtor, estado e métodos concretos, enquanto força subclasses a implementar o que ficou abstrato. Prefira abstract class quando há estado ou construtor compartilhado; prefira interface para contrato sem backing field; prefira sealed class quando a hierarquia precisa ser fechada para when exaustivo. Para o caminho completo de herança (open, override, super), veja o tutorial de herança em Kotlin.
abstract class Forma(val cor: String) {
abstract fun area(): Double
}
class Circulo(cor: String, val raio: Double) : Forma(cor) {
override fun area() = Math.PI * raio * raio
}
| Se você precisa de… | Use |
|---|---|
| Estado, construtor ou base compartilhada | abstract class |
| Contrato sem backing field + múltiplos contratos | interface |
Hierarquia fechada + when exaustivo | sealed class / sealed interface |
| Herança aberta com implementação completa | open class |
| Um único método abstrato + lambda | fun interface |
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
}
A classe abstrata define a estrutura e a lógica comum (descricao); cada subclasse concreta preenche só o que muda (area e perimetro).
Propriedades abstratas
Além 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á são implicitamente open, então subclasses podem herdá-las sem marcar nada extra. Membros abstratos também são automaticamente open. Já uma classe concreta só pode ser herdada se você marcar open — detalhe importante no tutorial de herança.
Abstract class vs Interface vs Sealed
| Aspecto | Abstract class | Interface | Sealed class |
|---|---|---|---|
| Construtor | Sim | Não | Sim |
| Estado (backing field) | Sim | Não | Sim |
| Herança múltipla | Não | Sim | Não |
| Métodos concretos | Sim | Sim (default) | Sim |
| Subtipos externos | Sim | Sim | Não (hierarquia fechada) |
when exaustivo | Não | Não | Sim |
Use abstract class quando subclasses compartilham estado e comportamento base. Para contratos puros sem estado, prefira interfaces. Quando o conjunto de subtipos precisa ser conhecido pelo compilador, use sealed class.
Quem vem do Java: o que muda no abstract
Muita gente chega em Kotlin já conhecendo abstract class do Java. O conceito é o mesmo — classe incompleta que não pode ser instanciada — mas a sintaxe é mais enxuta:
| No Java | Em Kotlin |
|---|---|
abstract class com construtor e campos protected | construtor primário direto na declaração: abstract class Forma(val cor: String) |
| Não existem propriedades abstratas (só getters abstratos em interfaces) | abstract val velocidadeMaxima: Int direto na classe |
abstract + classe precisa ser também marcada para herança | membros abstratos já são implicitamente open |
Classe anônima: new Forma() { ... } | expressão de objeto: object : Forma() { ... } |
| Uma classe pública por arquivo | quantas classes quiser no mesmo arquivo |
O equivalente da classe anônima do Java é a expressão object :, útil para implementar uma abstract class uma única vez, sem declarar subclasse:
val formaDescartavel = object : Forma("verde") {
override fun area() = 10.0
}
Em ambos os idiomas, membro abstrato não pode ser private (nem em Java nem em Kotlin ele compila), porque a subclasse precisa sobrescrevê-lo. Para o panorama completo da migração, veja Kotlin vs Java em 2026.
Sistema de notificações com classe abstrata
Um cenário real é construir um sistema de notificações em que cada tipo compartilha validação e metadados, mas tem o próprio 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 == '+' }
}
}
Repositório genérico com classe abstrata
Outro uso comum é criar uma camada de acesso a dados reutilizável com generics:
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 }
}
}
No Android, o próprio Room segue esse padrão: você declara abstract class AppDatabase : RoomDatabase() e o gerador completa a implementação. Veja o tutorial de Room.
Abstract class que também implementa interface
Dá para combinar os dois: a abstract class segura estado/construtor e ainda adota um contrato:
interface Identificavel {
val id: String
}
abstract class Entidade(override val id: String) : Identificavel {
abstract fun validar(): Boolean
fun salvarSeValida(): Boolean {
if (!validar()) return false
println("Salvando $id")
return true
}
}
class Produto(id: String, val preco: Double) : Entidade(id) {
override fun validar() = preco >= 0.0
}
Casos de uso no mundo real
- Frameworks de UI: bases abstratas definem ciclo de vida e hooks que cada tela concreta preenche.
- Persistência: repositórios e
RoomDatabaseabstratos padronizam CRUD e deixam o detalhe do banco para a implementação gerada ou concreta. - Backend / pipelines: etapas comuns (ler, transformar, gravar) ficam na base; cada fonte implementa só o específico.
- Testes: suites abstratas compartilham setup e asserts, enquanto subclasses testam implementações diferentes.
Boas práticas
- Use abstract class quando subclasses compartilham estado ou lógica de inicialização via construtor.
- Prefira interfaces para contrato sem estado — especialmente se a classe já herda de outra.
- Mantenha a base focada. Evite “super classes” que acumulam responsabilidades.
- Combine com generics para bases reutilizáveis e type-safe.
- Documente o contrato de cada membro abstrato: o que a subclasse deve garantir.
- Se a hierarquia for fechada e usada em
when, avalie sealed class antes de abstract aberta.
Erros comuns
- Instanciar a abstract class diretamente:
Forma("azul")não compila. Crie uma subclasse concreta. - Esquecer membros abstratos: se a subclasse não for abstrata, ela precisa implementar todos os métodos e propriedades abstratos.
- Usar abstract quando interface basta: sem estado nem construtor, interface é mais flexível (herança múltipla de contratos).
- Confundir
abstractcomopen:openpermite herança com implementação completa;abstractexige que a subclasse complete o que falta. - Escolher abstract em vez de sealed: se ninguém fora do módulo deveria criar subtipos, sealed deixa o
whenmais seguro.
Perguntas frequentes
Posso ter um construtor em uma classe abstrata?
Sim. Diferente de interfaces, classes abstratas suportam construtores com parâmetros. As subclasses devem chamar o construtor da base, como em class Circulo(cor: String) : Forma(cor).
Uma classe abstrata pode implementar uma interface? Sim. Ela pode implementar uma ou mais interfaces e até deixar alguns métodos sem implementação para as subclasses completarem.
Posso ter métodos concretos em uma classe abstrata? Sim — esse é um dos grandes diferenciais. Você mistura membros abstratos (sem corpo) com métodos concretos e reutiliza lógica comum.
Quando usar sealed class em vez de abstract class?
Use sealed quando quiser restringir quais subclasses existem e obter when exaustivo. Com abstract class, qualquer módulo pode criar uma subclasse; com sealed, os subtipos conhecidos ficam no mesmo pacote/módulo.
Próximos passos
- Interface — contrato sem estado,
fun interfacee SAM - Sealed class — hierarquia fechada com
whenexaustivo - Class — construtores, propriedades e o básico de OOP
- Generics — bases abstratas type-safe
- Tutorial de herança —
open,override,supere abstract na prática - Tutorial de interfaces — quando o contrato basta
- Classes e objetos — fundamentos antes da herança
- Kotlin vs Java — o que muda para quem migra de Java
- Room —
abstract classno Android