Capítulo 09: Herança, Polimorfismo, Classes Abstratas e Métodos Virtuais
Especialização em Sistemas com Java 21 LTS • Módulo de Herança e Contratos
🗺️ Mapa Conceitual do Tópico
classDiagram
class Colaborador {
<<abstract>>
-String matricula
-String nome
-BigDecimal salarioBase
+Colaborador(matricula, nome, salarioBase)
+calcularRemuneracaoTotal()* BigDecimal
+descreverHolerite() String
+getSalarioBase() BigDecimal
}
class EngenheiroSoftware {
-int horasPlantao
-BigDecimal valorHoraPlantao
+calcularRemuneracaoTotal() BigDecimal
}
class DiretorExecutivo {
-BigDecimal participacaoLucros
+calcularRemuneracaoTotal() BigDecimal
}
class ConsultorEspecialista {
-BigDecimal bonusDesempenho
+calcularRemuneracaoTotal() BigDecimal
}
Colaborador <|-- EngenheiroSoftware : "extends"
Colaborador <|-- DiretorExecutivo : "extends"
Colaborador <|-- ConsultorEspecialista : "extends"
note for Colaborador "Despacho Dinâmico (invokevirtual):
O tipo estático é Colaborador,
mas o método executado depende do
tipo dinâmico na Heap (vtable lookup)"
🏛️ 1. Herança e o Princípio de Substituição de Liskov (LSP)
Em Java, a herança é estabelecida através da palavra-chave extends, permitindo que uma subclasse herde atributos e métodos de uma única superclasse direta (herança simples de implementação). Todas as classes em Java herdam implicitamente de java.lang.Object.
O Princípio de Substituição de Liskov (SOLID - L):
“Se $S$ é um subtipo de $T$, então objetos do tipo $T$ devem poder ser substituídos por objetos do tipo $S$ sem quebrar a consistência ou corretude do sistema.”
Para cumprir o LSP em Java:
- Uma subclasse não pode impor pré-condições mais restritivas que a superclasse.
- Não pode enfraquecer pós-condições (ex.: retornar tipos incompatíveis ou lançar novas exceções checadas não declaradas).
- Deve preservar os invariantes estabelecidos pela superclasse.
O Modificador final em Classes e Métodos:
- Classes
final: Impedem qualquer tentativa de herança (ex:java.lang.String,java.lang.Integer). Garante imutabilidade e viabiliza agressivas otimizações de inlining pelo compilador JIT (C2). - Métodos
final: Não podem ser sobrescritos pelas subclasses.
🎭 2. Polimorfismo e Despacho Dinâmico de Métodos (vtable)
O polimorfismo permite que referências de um tipo mais genérico manipulem instâncias de tipos mais especializados em tempo de execução:
Colaborador dev = new EngenheiroSoftware("ENG-01", "Lucas", new BigDecimal("8000.00"), 10);
dev.calcularRemuneracaoTotal(); // Executa o método de EngenheiroSoftware!
Como a JVM Executa o Despacho Dinâmico:
- Tipo Estático: É o tipo declarado da variável (
Colaborador). É checado pelo compiladorjavacem tempo de compilação. - Tipo Dinâmico: É a classe concreta do objeto alocado na Heap (
EngenheiroSoftware). - Instrução
invokevirtual: No bytecode, a chamada a métodos não-privados e não-estáticos utilizainvokevirtual. A JVM consulta uma tabela interna chamada vtable (Virtual Method Table) associada ao ponteiro de classe do objeto (klass pointer), localizando o endereço exato da implementação da subclasse.
🧱 3. Classes Abstratas vs Interfaces
Uma classe anotada com abstract atua como uma fundação parcial para uma hierarquia:
- Não Instanciável: A tentativa de executar
new Colaborador(...)resulta em erro de compilação. - Compartilhamento de Estado: Ao contrário de interfaces clássicas, classes abstratas podem conter atributos de instância protegidos ou privados, construtores e blocos de inicialização.
- Construtor Obrigatório: Construtores em classes abstratas são acionados exclusivamente a partir dos construtores das subclasses via instrução
super(...).
📊 4. Matriz Comparativa: Concreta vs Abstrata vs Record
| Aspecto | Classe Concreta | Classe Abstrata | Record (Java 16+) |
|---|---|---|---|
Instanciação Direta (new) |
Sim | Não | Sim |
| Pode Conter Métodos Abstratos | Não | Sim | Não |
Pode Ser Estendida (extends) |
Sim (a menos de final) |
Sim | Não (é implicitamente final) |
| Pode Herdar de Outra Classe | Sim (apenas uma) | Sim (apenas uma) | Não (já herda de java.lang.Record) |
| Atributos de Instância Mutáveis | Sim | Sim | Não (todos os componentes são final) |
| Finalidade Arquitetural | Entidade de domínio comum | Esqueleto/Template com estado compartilhado | DTO imutável e portador transparente de dados |
💻 5. Código de Demonstração Corporativo (Java 21 LTS)
package com.empresa.heranca;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.Objects;
/**
* Hierarquia polimórfica de remuneração corporativa com despacho dinâmico.
*/
public class SistemaFolhaPolimorfica {
public static abstract class Colaborador {
private final String matricula;
private final String nome;
private final BigDecimal salarioBase;
public Colaborador(String matricula, String nome, BigDecimal salarioBase) {
this.matricula = Objects.requireNonNull(matricula, "Matrícula obrigatória.");
this.nome = Objects.requireNonNull(nome, "Nome obrigatório.");
this.salarioBase = Objects.requireNonNull(salarioBase, "Salário base obrigatório.");
}
// Método Abstrato polimórfico obrigatório nas subclasses
public abstract BigDecimal calcularRemuneracaoTotal();
// Template Method concreto
public String emitirExtrato() {
return String.format("[%s] %-25s | Salário Base: R$ %9.2f | Remuneração Total: R$ %9.2f",
matricula, nome, salarioBase, calcularRemuneracaoTotal());
}
public String getMatricula() { return matricula; }
public String getNome() { return nome; }
public BigDecimal getSalarioBase() { return salarioBase; }
}
public static class EngenheiroSoftware extends Colaborador {
private final int horasSobreaviso;
private final BigDecimal valorHoraSobreaviso;
public EngenheiroSoftware(String matricula, String nome, BigDecimal salarioBase, int horasSobreaviso) {
super(matricula, nome, salarioBase);
this.horasSobreaviso = horasSobreaviso;
this.valorHoraSobreaviso = new BigDecimal("85.00");
}
@Override
public BigDecimal calcularRemuneracaoTotal() {
BigDecimal adicionalPlantao = valorHoraSobreaviso.multiply(new BigDecimal(horasSobreaviso));
return getSalarioBase().add(adicionalPlantao).setScale(2, RoundingMode.HALF_UP);
}
}
public static class DiretorExecutivo extends Colaborador {
private final BigDecimal participacaoLucros;
public DiretorExecutivo(String matricula, String nome, BigDecimal salarioBase, BigDecimal participacaoLucros) {
super(matricula, nome, salarioBase);
this.participacaoLucros = participacaoLucros;
}
@Override
public BigDecimal calcularRemuneracaoTotal() {
return getSalarioBase().add(participacaoLucros).setScale(2, RoundingMode.HALF_UP);
}
}
public static void main(String[] args) {
System.out.println("================================================================================");
System.out.println(" PROCESSAMENTO POLIMÓRFICO DA FOLHA DE PAGAMENTO (JAVA 21) ");
System.out.println("================================================================================");
List<Colaborador> equipe = List.of(
new EngenheiroSoftware("ENG-101", "Ana Paula Ramos", new BigDecimal("9500.00"), 18),
new EngenheiroSoftware("ENG-102", "Carlos Eduardo Lima", new BigDecimal("7200.00"), 0),
new DiretorExecutivo("DIR-001", "Roberto Drummond", new BigDecimal("28000.00"), new BigDecimal("15000.00"))
);
BigDecimal custoTotalFolha = BigDecimal.ZERO;
for (Colaborador c : equipe) {
System.out.println(c.emitirExtrato()); // Despacho dinâmico via invokevirtual
custoTotalFolha = custoTotalFolha.add(c.calcularRemuneracaoTotal());
}
System.out.println("--------------------------------------------------------------------------------");
System.out.printf("CUSTO CONSOLIDADO TOTAL DA FOLHA : R$ %,10.2f%n", custoTotalFolha);
System.out.println("================================================================================");
}
}