O que é Interface em Kotlin?

Uma interface em Kotlin define um contrato que classes devem seguir. Ela declara quais funções e propriedades uma classe precisa ter, mas sem obrigar uma implementação específica. Diferente de classes abstratas, uma classe pode implementar várias interfaces.

Sintaxe básica

interface Autenticavel {
    fun autenticar(senha: String): Boolean
}

class Usuario(val nome: String, private val senhaHash: String) : Autenticavel {
    override fun autenticar(senha: String): Boolean {
        return senha == senhaHash
    }
}

fun main() {
    val user = Usuario("Karina", "abc123")
    println(user.autenticar("abc123")) // true
    println(user.autenticar("errada")) // false
}

Métodos com implementação padrão

Em Kotlin, interfaces podem ter métodos com corpo — os famosos default methods:

interface Logavel {
    val tag: String
        get() = this::class.simpleName ?: "Desconhecido"

    fun log(mensagem: String) {
        println("[$tag] $mensagem")
    }
}

class Servico : Logavel

fun main() {
    val servico = Servico()
    servico.log("Iniciando processamento")
    // [Servico] Iniciando processamento
}

Múltiplas interfaces

interface Imprimivel {
    fun imprimir()
}

interface Exportavel {
    fun exportar(): String
}

class Relatorio(val titulo: String) : Imprimivel, Exportavel {
    override fun imprimir() {
        println("Imprimindo: $titulo")
    }

    override fun exportar(): String {
        return "PDF: $titulo"
    }
}

fun main() {
    val relatorio = Relatorio("Vendas Q1")
    relatorio.imprimir()
    println(relatorio.exportar())
}

Resolvendo conflitos

Quando duas interfaces têm o mesmo método com implementação padrão, você precisa resolver o conflito manualmente:

interface A {
    fun saudacao() = "Olá de A"
}

interface B {
    fun saudacao() = "Olá de B"
}

class C : A, B {
    override fun saudacao(): String {
        return "${super<A>.saudacao()} e ${super<B>.saudacao()}"
    }
}

Quando usar interface vs abstract class?

Use interface quando quiser definir um contrato que múltiplas classes podem adotar. Use abstract class quando precisar de estado compartilhado (campos com valores) ou construtores. Interfaces são mais flexíveis e promovem um design mais desacoplado.

Interface com propriedades abstratas

Interfaces podem declarar propriedades que as classes implementadoras devem fornecer:

interface Identificavel {
    val id: String
    val tipo: String
        get() = "genérico" // Propriedade com valor padrao
}

interface Auditavel {
    val criadoEm: Long
    val atualizadoEm: Long
}

data class Documento(
    override val id: String,
    val titulo: String,
    override val criadoEm: Long,
    override val atualizadoEm: Long
) : Identificavel, Auditavel {
    override val tipo: String
        get() = "documento"
}

fun main() {
    val doc = Documento("doc-001", "Contrato", 1700000000, 1700100000)
    println("${doc.tipo}: ${doc.titulo} (ID: ${doc.id})")
    // documento: Contrato (ID: doc-001)
}

Interface como tipo para injeção de dependência

Um dos usos mais poderosos de interfaces é permitir a substituição de implementações, fundamental para testes e arquiteturas desacopladas:

interface NotificacaoService {
    fun enviar(destinatario: String, mensagem: String): Boolean
}

class EmailService : NotificacaoService {
    override fun enviar(destinatario: String, mensagem: String): Boolean {
        println("Enviando email para $destinatario: $mensagem")
        return true
    }
}

class SmsService : NotificacaoService {
    override fun enviar(destinatario: String, mensagem: String): Boolean {
        println("Enviando SMS para $destinatario: $mensagem")
        return true
    }
}

// Em testes, voce pode criar um fake facilmente
class FakeNotificacaoService : NotificacaoService {
    val mensagensEnviadas = mutableListOf<Pair<String, String>>()

    override fun enviar(destinatario: String, mensagem: String): Boolean {
        mensagensEnviadas.add(destinatario to mensagem)
        return true
    }
}

class PedidoService(private val notificacao: NotificacaoService) {
    fun finalizarPedido(clienteEmail: String) {
        // ... logica do pedido
        notificacao.enviar(clienteEmail, "Seu pedido foi confirmado!")
    }
}

Sealed interfaces

A partir do Kotlin 1.5, você pode usar sealed interfaces para restringir quais classes podem implementar uma interface:

sealed interface Resultado {
    data class Sucesso(val dados: String) : Resultado
    data class Erro(val mensagem: String) : Resultado
    data object Carregando : Resultado
}

fun processar(resultado: Resultado) {
    when (resultado) {
        is Resultado.Sucesso -> println("Dados: ${resultado.dados}")
        is Resultado.Erro -> println("Erro: ${resultado.mensagem}")
        Resultado.Carregando -> println("Carregando...")
    }
}

O when é exaustivo automaticamente para sealed interfaces, dispensando o ramo else.

Casos de Uso no Mundo Real

  • Camada de repositório: definir uma interface UsuarioRepository com operações como buscarPorId, salvar e deletar, permitindo trocar a implementação entre banco de dados real e repositório em memória para testes.
  • Strategy pattern em regras de negócio: criar uma interface CalculoFrete com diferentes implementações para Correios, transportadora e retirada, injetando a estratégia correta conforme a escolha do cliente.
  • Plugins e extensibilidade: sistemas de plugins usam interfaces para definir contratos que módulos externos devem seguir, permitindo adicionar funcionalidades sem modificar o código principal.
  • Tratamento de estado com sealed interface: modelar estados de tela no Android (Loading, Success, Error) como uma sealed interface, garantindo que o when cubra todos os estados possíveis.

Boas Práticas

  • Prefira interfaces pequenas e focadas (Interface Segregation Principle). Uma interface com muitos métodos força implementadores a lidar com funcionalidades que podem não ser relevantes.
  • Use implementações padrão com moderação. Elas são úteis para métodos auxiliares, mas não devem conter lógica de negócio complexa que seria melhor em uma classe abstrata.
  • Nomeie interfaces pelo comportamento que descrevem, não pela implementação: Serializavel, Persistivel, Autenticavel comunicam intenção melhor que SerializadorImpl ou BancoDeDados.
  • Programe para a interface, não para a implementação. Declare variáveis e parâmetros usando o tipo da interface sempre que possível.
  • Use sealed interfaces quando o conjunto de implementações é fechado e conhecido, permitindo when exaustivo e maior segurança em tempo de compilação.

Erros Comuns

  • Criar interfaces com um único implementador sem justificativa: se a interface existe apenas por existir e nunca será reimplementada, ela adiciona complexidade sem benefício. Crie interfaces quando houver necessidade real de abstração.
  • Colocar estado mutável em interfaces: interfaces não devem ter campos var com backing field. Se precisa de estado compartilhado, use uma classe abstrata.
  • Esquecer o override ao implementar métodos da interface: diferente de Java, Kotlin exige a palavra-chave override explicitamente, o que previne erros acidentais.
  • Não resolver conflitos de nomes entre interfaces: quando duas interfaces têm métodos com o mesmo nome e implementação padrão, a classe deve resolver explicitamente com super<Interface>.método().
  • Usar interface quando abstract class seria mais apropriada: se as implementações compartilham estado (propriedades com backing field) ou lógica de construtor, uma classe abstrata é a escolha mais adequada.

Perguntas Frequentes

Interfaces em Kotlin podem ter construtores? Não. Interfaces não podem ter construtores. Se você precisa de parâmetros de inicialização, use uma classe abstrata.

Qual a diferença entre interface em Kotlin e em Java? Em Kotlin, interfaces podem ter propriedades (sem backing field), implementações padrão de métodos e participar de sealed hierarchies. Em Java, default methods existem desde o Java 8, mas propriedades em interfaces não são suportadas.

Posso usar delegação com interfaces? Sim. Kotlin suporta delegação de interface com a palavra-chave by: class MeuServico(servico: NotificacaoService) : NotificacaoService by servico. Isso delega todas as chamadas de interface para o objeto fornecido.

Quando usar sealed interface vs sealed class? Use sealed interface quando as implementações não precisam compartilhar estado (propriedades com backing field) e quando deseja que uma classe implemente múltiplas sealed hierarchies. Sealed class é melhor quando há estado e lógica compartilhados.

Termos Relacionados

  • Fun — funções declaradas em interfaces são implementadas usando fun e override
  • Generics — interfaces genéricas como Comparable<T> e Iterable<T> definem contratos flexíveis
  • Lambda — interfaces funcionais (SAM) podem ser substituídas por lambdas em Kotlin
  • Higher-Order Function — interfaces com um único método abstrato podem ser usadas como tipos de função