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 compartilhadaabstract class
Contrato sem backing field + múltiplos contratosinterface
Hierarquia fechada + when exaustivosealed class / sealed interface
Herança aberta com implementação completaopen class
Um único método abstrato + lambdafun 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

AspectoAbstract classInterfaceSealed class
ConstrutorSimNãoSim
Estado (backing field)SimNãoSim
Herança múltiplaNãoSimNão
Métodos concretosSimSim (default)Sim
Subtipos externosSimSimNão (hierarquia fechada)
when exaustivoNãoNãoSim

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 JavaEm Kotlin
abstract class com construtor e campos protectedconstrutor 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çamembros abstratos já são implicitamente open
Classe anônima: new Forma() { ... }expressão de objeto: object : Forma() { ... }
Uma classe pública por arquivoquantas 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 RoomDatabase abstratos 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 abstract com open: open permite herança com implementação completa; abstract exige 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 when mais 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