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

AspectoAbstract ClassInterface
ConstrutorSimNao
Estado (campos)SimNao (só propriedades)
Heranca multiplaNaoSim
Metodos concretosSimSim

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 Activity e Fragment no 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 abstract com open: open permite que uma classe seja herdada mas fornece implementação completa. abstract exige 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.