Kotlin: Herança e Interfaces do Kotlin Explicadas
Última atualização: 2026-08-26
O modelo de herança do Kotlin é mais seguro que o do Java: classes são final por padrão e métodos não são sobrescrevíveis por padrão — Charlie deve declarar explicitamente open para permitir herança, com o compilador protegendo a segurança do polimorfismo.
1. O que Você Aprenderá
open/abstract: classes são final por padrão, requer abertura explícita- Implementações padrão de interfaces
- Anotação obrigatória
override - Resolução de conflitos de herança múltipla:
super<ParentA> - Charlie em ação: hierarquia de herança BulkOrder / PriorityOrder
2. A História Real de um Arquiteto
(1) Problema: Sobrescrita Acidental Causando Incidentes em Produção
Bob herdou a classe Order para criar SpecialOrder e acidentalmente sobrescreveu o método calculateTotal — causando 500 pedidos especiais com valores incorretos. A equipe financeira emitiu um alerta urgente.
(2) Guarda em Tempo de Compilação do Kotlin
// Java: anotação override é opcional - sobrescrita silenciosa possível
class SpecialOrder extends Order {
double calculateTotal() { ... } // Sobrescreve silenciosamente!
}
// Kotlin: override é OBRIGATÓRIO - compilador captura erros
open class Order {
open fun calculateTotal(): Double = ...
}
class SpecialOrder : Order() {
override fun calculateTotal(): Double = ... // Deve declarar override
}
Anotação obrigatória
override+ classes final por padrão = compilador protege a segurança da herança. Bob nunca mais fará sobrescrita acidental.
3. Modelo de Herança Open
(1) Classes Final por Padrão
// Padrão: classe é final (não pode ser herdada)
class Order(val id: String) // Não pode ser estendida
// Open: permitir herança explicitamente
open class Order(val id: String) {
// Métodos também são final por padrão
fun process() { } // Não pode ser sobrescrito
// Método open: permitir sobrescrita explicitamente
open fun calculateTotal(): Double = 0.0
}
// Abstract: deve ser herdada, não pode ser instanciada
abstract class BaseOrder(val id: String) {
// Método abstrato: deve ser implementado pela subclasse
abstract fun calculateTotal(): Double
// Método concreto em classe abstrata
fun getId(): String = id
}
(2) Hierarquia de Modificadores de Herança
| Modificador | Classe | Método | Instanciável | Herdável | Sobrescrevível |
|---|---|---|---|---|---|
| Padrão (final) | ✅ | ✅ | ✅ | ❌ | ❌ |
open |
✅ | ✅ | ✅ | ✅ | ✅ |
abstract |
✅ | ✅ | ❌ | ✅ (obrigatório) | ✅ (obrigatório) |
(3) Comparação de Herança: Java vs Kotlin
| Dimensão | Java | Kotlin |
|---|---|---|
| Padrão de classe | Herdável | final (não herdável) |
| Padrão de método | Sobrescrevível | final (não sobrescrevível) |
| Anotação override | @Override (opcional) |
override (obrigatório) |
| Herança aberta | Nenhuma declaração necessária | Declaração explícita open |
| Filosofia de design | "Aberto por padrão" | "Fechado por padrão" |
4. Interfaces e Implementações Padrão
(1) Definição de Interface
interface Discountable {
// Método abstrato
fun discountRate(): Double
// Implementação padrão
fun applyDiscount(total: Double): Double {
return total * (1.0 - discountRate())
}
}
interface Trackable {
val trackingCode: String? // Propriedade abstrata
fun track(): String = trackingCode?.let { "Rastreamento: $it" } ?: "Ainda não enviado"
}
// Implementar múltiplas interfaces
class PremiumOrder(
val id: String,
val total: Double,
override val trackingCode: String?
) : Discountable, Trackable {
override fun discountRate() = 0.15 // 15% de desconto
}
(2) Interface vs Classe Abstrata
| Dimensão | Interface | Classe Abstrata |
|---|---|---|
| Construtor | Nenhum | Tem |
| Estado | Nenhum (apenas propriedades abstratas) | Tem (propriedades concretas) |
| Herança múltipla | Suportada (múltiplas implementações) | Não suportada (herança única) |
| Implementação padrão | Kotlin suporta | Suportado |
| Caso de uso | Contrato de comportamento | Estado compartilhado + comportamento |
5. Anotação Obrigatória override
(1) Uso Básico
open class Order(val id: String, var status: String) {
open fun summary(): String = "Pedido $id: $status"
}
class BulkOrder(id: String, status: String, val minQuantity: Int) : Order(id, status) {
// DEVE usar a palavra-chave override
override fun summary(): String = "Atacado $id: $status (mín: $minQuantity)"
}
(2) Impedir Sobrescrita Adicional
open class Order {
open fun process() { }
}
class SpecialOrder : Order() {
// Impedir sobrescrita adicional em subclasses
final override fun process() { }
}
6. Resolução de Conflitos de Herança Múltipla
(1) Problema da Herança em Diamante
interface A {
fun hello() = "A"
}
interface B {
fun hello() = "B"
}
// Diamante: C herda hello() de ambos A e B
class C : A, B {
// DEVE sobrescrever para resolver o conflito
override fun hello(): String {
// Escolher explicitamente qual implementação do pai
return super<A>.hello() // Escolher a implementação de A
}
}
(2) Estratégias de Resolução de Conflitos
class D : A, B {
override fun hello(): String {
// Estratégia 1: Escolher um pai
return super<B>.hello()
// Estratégia 2: Combinar ambos
// return "${super<A>.hello()} + ${super<B>.hello()}"
// Estratégia 3: Implementação personalizada
// return "D"
}
}
(3) Comparação de Resolução de Conflitos
| Estratégia | Sintaxe | Caso de Uso |
|---|---|---|
| Escolher pai A | super<A>.hello() |
Implementação de A é mais apropriada |
| Escolher pai B | super<B>.hello() |
Implementação de B é mais apropriada |
| Combinar ambos | super<A> + super<B> |
Precisa de ambos os comportamentos |
| Implementação personalizada | Código personalizado | Nenhuma é apropriada |
7. Diagrama de Hierarquia de Herança
classDiagram
class Order {
+val id: String
+var status: String
+open fun summary(): String
+open fun calculateTotal(): Double
}
class BulkOrder {
+val minQuantity: Int
+override fun calculateTotal(): Double
}
class PriorityOrder {
+val priority: String
+override fun summary(): String
}
class Discountable {
<<interface>>
+fun discountRate(): Double
+fun applyDiscount(total: Double): Double
}
class Trackable {
<<interface>>
+val trackingCode: String?
+fun track(): String
}
Order <|-- BulkOrder
Order <|-- PriorityOrder
BulkOrder ..|> Discountable
PriorityOrder ..|> Trackable
PriorityOrder ..|> Discountable
8. Exemplo Completo: Hierarquia de Herança do OrderProcessor
// ============================================
// OrderProcessor - Hierarquia de Herança
// Funcionalidade: Order / BulkOrder / PriorityOrder
// ============================================
interface Discountable {
fun discountRate(): Double
fun applyDiscount(total: Double): Double = total * (1.0 - discountRate())
}
interface Trackable {
val trackingCode: String?
fun track(): String = trackingCode?.let { "Rastreamento: $it" } ?: "Ainda não enviado"
}
open class Order(val id: String, var status: String, val total: Double) {
init {
require(total >= 0) { "Total deve ser não negativo" }
}
open fun summary(): String = "Pedido $id | \$$total USD | $status"
open fun calculateFinal(): Double = total
}
class BulkOrder(
id: String,
status: String,
total: Double,
val minQuantity: Int
) : Order(id, status, total), Discountable {
override fun discountRate() = 0.10 // 10% de desconto por quantidade
override fun calculateFinal(): Double = applyDiscount(total)
override fun summary(): String =
"${super.summary()} | Atacado (mín: $minQuantity) | Final: \$${calculateFinal()} USD"
}
class PriorityOrder(
id: String,
status: String,
total: Double,
val priority: String,
override val trackingCode: String?
) : Order(id, status, total), Discountable, Trackable {
override fun discountRate() = when (priority) {
"VIP" -> 0.20
"GOLD" -> 0.15
else -> 0.05
}
override fun calculateFinal(): Double = applyDiscount(total)
override fun summary(): String =
"${super.summary()} | Prioridade: $priority | ${track()} | Final: \$${calculateFinal()} USD"
}
fun main() {
val orders = listOf(
Order("ORD-001", "CONFIRMED", 299.99),
BulkOrder("ORD-002", "CONFIRMED", 5_000.00, 100),
PriorityOrder("ORD-003", "SHIPPED", 15_000.00, "VIP", "TRK-XYZ789"),
PriorityOrder("ORD-004", "PENDING", 2_500.00, "GOLD", null)
)
println("=== Resumo de Pedidos ===")
orders.forEach { println(it.summary()) }
val totalSavings = orders
.filterIsInstance<Discountable>()
.sumOf { it.applyDiscount((it as Order).total) - (it as Order).calculateFinal() }
println("\nEconomia total com descontos: \$$totalSavings USD")
}
Saída:
=== Resumo de Pedidos ===
Pedido ORD-001 | $299.99 USD | CONFIRMED
Pedido ORD-002 | $5000.0 USD | CONFIRMED | Atacado (mín: 100) | Final: $4500.0 USD
Pedido ORD-003 | $15000.0 USD | SHIPPED | Prioridade: VIP | Rastreamento: TRK-XYZ789 | Final: $12000.0 USD
Pedido ORD-004 | $2500.0 USD | PENDING | Prioridade: GOLD | Ainda não enviado | Final: $2125.0 USD
Economia total com descontos: $3475.0 USD
9. Exemplos práticos rápidos
▶ Exemplo: Herança simples
// Por padrão todas as classes são final - devem ser marcadas open para herança
open class Shape(val name: String) {
open fun area(): Double = 0.0
fun describe() = "Forma: $name, área=${area()}"
}
class Circle(name: String, val radius: Double) : Shape(name) {
override fun area(): Double = Math.PI * radius * radius
}
class Rectangle(name: String, val width: Double, val height: Double) : Shape(name) {
override fun area(): Double = width * height
}
val shapes = listOf(
Circle("círculo", 5.0),
Rectangle("retângulo", 4.0, 6.0)
)
shapes.forEach { println(it.describe()) }
Saída:
Forma: círculo, área=78.53981633974483
Forma: retângulo, área=24.0
▶ Exemplo: Interfaces
// Interface pode ter propriedades abstratas e implementações padrão
interface Printable {
fun print()
fun preview(): String = "Prévia de $this"
}
interface Loggable {
fun log() = println("[LOG] $this")
}
class Document(val title: String, val content: String) : Printable, Loggable {
override fun print() {
println("== $title ==")
println(content)
}
override fun preview() = "Prévia: ${title.substring(0, minOf(title.length, 20))}"
}
val doc = Document("Relatório Anual", "Receita total: $1M")
println(doc.preview())
doc.print()
doc.log()
Saída:
Prévia: Relatório Anual
== Relatório Anual ==
Receita total: $1M
[LOG] Document(title=Relatório Anual, content=Receita total: $1M)
▶ Exemplo: Conflitos em interfaces
interface A {
fun greet() = println("Olá de A")
}
interface B {
fun greet() = println("Olá de B")
}
class C : A, B {
// Deve resolver conflito explicitamente
override fun greet() {
super<A>.greet()
super<B>.greet()
println("Olá de C")
}
}
val c = C()
c.greet()
Saída:
Olá de A
Olá de B
Olá de C
▶ Exemplo: Classes abstratas
abstract class Vehicle(val maxSpeed: Double) {
abstract fun move(): String
abstract fun fuelType(): String
fun describe() = "${this::class.simpleName}: ${move()}, combustível=${fuelType()}, maxSpeed=${maxSpeed}km/h"
}
class Car(maxSpeed: Double, val fuel: String) : Vehicle(maxSpeed) {
override fun move() = "Dirigindo"
override fun fuelType() = fuel
}
class Boat(maxSpeed: Double) : Vehicle(maxSpeed) {
override fun move() = "Navegando"
override fun fuelType() = "Diesel"
}
val vehicles = listOf(
Car(180.0, "Gasolina"),
Car(220.0, "Elétrico"),
Boat(60.0)
)
vehicles.forEach { println(it.describe()) }
Saída:
Car: Dirigindo, combustível=Gasolina, maxSpeed=180.0km/h
Car: Dirigindo, combustível=Elétrico, maxSpeed=220.0km/h
Boat: Navegando, combustível=Diesel, maxSpeed=60.0km/h
▶ Exemplo: Polimorfismo e upcasting
open class Animal(val name: String) {
open fun speak() = "$name faz som"
}
class Dog(name: String) : Animal(name) {
override fun speak() = "$name: Au au!"
}
class Cat(name: String) : Animal(name) {
override fun speak() = "$name: Miau!"
}
// Upcasting: Dog/Cat tratados como Animal
val animals: List<Animal> = listOf(
Dog("Rex"),
Cat("Whiskers"),
Dog("Buddy")
)
animals.forEach { println(it.speak()) }
// Usando `is` para downcast com segurança
animals.forEach { animal ->
when (animal) {
is Dog -> println("${animal.name} é um cachorro")
is Cat -> println("${animal.name} é um gato")
}
}
Saída:
Rex: Au au!
Whiskers: Miau!
Buddy: Au au!
Rex é um cachorro
Whiskers é um gato
Buddy é um cachorro
▶ Exemplo: Modificadores de herança open/final/sealed
// sealed: subclasses restritas ao mesmo arquivo (usado em when exaustivo)
sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Failure(val error: String) : Result<Nothing>()
}
fun handle(result: Result<Int>) = when (result) {
is Result.Success -> "OK: ${result.data}"
is Result.Failure -> "Erro: ${result.error}"
// Compilador sabe que ambos os casos estão cobertos - não precisa de `else`
}
println(handle(Result.Success(42)))
println(handle(Result.Failure("Falha de rede")))
// final (padrão): não pode ser herdada
// open: pode ser herdada
// abstract: deve ser herdada para usar
// sealed: hierarquia fechada (arquivo/módulo)
Saída:
OK: 42
Erro: Falha de rede
▶ Exemplo: Sobrescrita e polimorfismo
// Métodos da superclasse acessíveis via super
open class Account(val id: String, protected var balance: Double = 0.0) {
open fun deposit(amount: Double) {
balance += amount
println("[$id] Depósito: \$$amount, novo saldo: \$$balance")
}
open fun getType() = "Conta"
}
class SavingsAccount(id: String, balance: Double, val interestRate: Double) : Account(id, balance) {
override fun deposit(amount: Double) {
super.deposit(amount)
val interest = amount * interestRate
balance += interest
println("[$id] Juros adicionados: \$$interest")
}
override fun getType() = "Conta Poupança"
}
val savings = SavingsAccount("SAV-001", 1000.0, 0.005)
savings.deposit(500.0)
println("Tipo: ${savings.getType()}")
Saída:
[SAV-001] Depósito: $500.0, novo saldo: $1500.0
[SAV-001] Juros adicionados: $2.5
Tipo: Conta Poupança
❓ Perguntas Frequentes
P: Por que as classes Kotlin são final por padrão? R: Effective Java recomenda "projetar e documentar para herança, ou proibi-la". O Kotlin adota este conselho, padronizando para proibir herança, usando
openpara abertura explícita.
P: Interfaces podem ter construtores? R: Não. Interfaces não têm construtores e não podem manter estado. Use classes abstratas para estado compartilhado.
P: Uma classe pode resolver conflitos ao implementar múltiplas interfaces com métodos padrão? R: Sim. Se duas interfaces têm métodos padrão com o mesmo nome e assinatura, a subclasse deve sobrescrever o método e usar
super<InterfaceName>para escolher uma implementação.
P:
overridepode ser omitido? R: Não. O Kotlin exige a palavra-chaveoverride— omiti-la causa um erro de compilação. Este é o mecanismo de segurança chave para prevenir sobrescritas acidentais.
P: Métodos de classes abstratas precisam de
open? R: Não. Métodos abstratos em classes abstratas são implicitamente sobrescrevíveis (devem ser sobrescritos). Métodos open não abstratos ainda precisam do modificadoropen.
P: O Kotlin suporta chamar
supercomo Java? R: Sim. Construtores de subclasse usamsuper(...)para chamar o construtor pai, métodos usamsuper.foo()para chamar a implementação pai, e interfaces usamsuper<Interface>.foo().
📖 Resumo
- Classes Kotlin são final por padrão; devem ser
openpara herança — fechadas por padrão, abertas explicitamente - Classes
abstractnão podem ser instanciadas; métodos abstratos devem ser implementados por subclasses - Interfaces suportam implementações padrão e propriedades abstratas; implementação múltipla é permitida
- Anotação obrigatória
overrideprevine sobrescritas acidentais — o compilador protege a segurança do polimorfismo - Conflitos de herança múltipla são resolvidos com seleção explícita
super<Interface> - Use interfaces para contratos de comportamento (sem estado), classes abstratas para estado compartilhado + comportamento
📝 Exercícios
- Iniciante (⭐): Defina um
Ordere umBulkOrderherdando dele, sobrescrevendo o métodosummary. Dica:open class Order,class BulkOrder : Order() - Intermediário (⭐⭐): Crie interfaces
DiscountableeTrackable, façaPriorityOrderimplementar ambas, resolvendo um conflito de nome. Dica:super<Discountable> - Desafio (⭐⭐⭐): Projete uma hierarquia de herança de pedidos completa:
BaseOrder→BulkOrder/PriorityOrder, cada um implementando diferentes estratégias de desconto e envio. Dica: combine interfaces e classes abstratas