Polimorfismo
O Terceiro Pilar da Programação Orientada a Objetos - Um mesmo método, múltiplos comportamentos
1. O que é Polimorfismo?
Polimorfismo vem do grego "poli" (muitos) + "morphos" (formas). É a capacidade de um objeto assumir múltiplas formas ou um mesmo método ter comportamentos diferentes.
Em outras palavras, polimorfismo permite que você use a mesma interface para diferentes tipos de dados, e cada tipo responde de forma apropriada.
Existem dois tipos principais de polimorfismo: Polimorfismo de Compilação (Sobrecarga) e Polimorfismo de Execução (Sobrescrita).
2. Tipos de Polimorfismo
1. Polimorfismo de Compilação (Compile-time)
Sobrecarga (Overload): Múltiplos métodos com mesmo nome, mas parâmetros diferentes. Decidido em tempo de compilação.
2. Polimorfismo de Execução (Runtime)
Sobrescrita (Override): Subclasses implementam métodos da classe pai de forma diferente. Decidido em tempo de execução.
3. Polimorfismo de Compilação - Sobrecarga (Overload)
Sobrecarga permite ter múltiplos métodos com o mesmo nome, mas diferindo em:
- Número de parâmetros: Diferentes quantidades de argumentos
- Tipo de parâmetros: Tipos diferentes nos mesmos índices
- Ordem de parâmetros: Mesmos tipos em ordem diferente
Exemplo 1: Número de Parâmetros
public class Calculadora {
// Método com 2 parâmetros
public int somar(int a, int b) {
return a + b;
}
// Método com 3 parâmetros (OVERLOAD)
public int somar(int a, int b, int c) {
return a + b + c;
}
// Método com 4 parâmetros (OVERLOAD)
public int somar(int a, int b, int c, int d) {
return a + b + c + d;
}
}
// Uso
Calculadora calc = new Calculadora();
System.out.println(calc.somar(5, 3)); // 8
System.out.println(calc.somar(5, 3, 2)); // 10
System.out.println(calc.somar(5, 3, 2, 1)); // 11
Exemplo 2: Tipo de Parâmetros
public class Impressora {
// Imprime inteiro
public void imprimir(int valor) {
System.out.println("Imprimindo inteiro: " + valor);
}
// Imprime double (OVERLOAD)
public void imprimir(double valor) {
System.out.println("Imprimindo double: " + valor);
}
// Imprime String (OVERLOAD)
public void imprimir(String valor) {
System.out.println("Imprimindo string: " + valor);
}
// Imprime boolean (OVERLOAD)
public void imprimir(boolean valor) {
System.out.println("Imprimindo boolean: " + valor);
}
}
// Uso - Java escolhe o método correto
Impressora printer = new Impressora();
printer.imprimir(42); // Imprimindo inteiro: 42
printer.imprimir(3.14); // Imprimindo double: 3.14
printer.imprimir("Olá"); // Imprimindo string: Olá
printer.imprimir(true); // Imprimindo boolean: true
Exemplo 3: Ordem de Parâmetros
public class Coordenadas {
// Método com String, int
public void exibir(String nome, int valor) {
System.out.println(nome + ": " + valor);
}
// Método com int, String (OVERLOAD - ordem diferente)
public void exibir(int valor, String nome) {
System.out.println("Valor " + valor + " de " + nome);
}
}
// Uso
Coordenadas coord = new Coordenadas();
coord.exibir("X", 10); // X: 10
coord.exibir(10, "X"); // Valor 10 de X
⚠️ O que NÃO é Sobrecarga:
// ERRADO - Apenas mudar tipo de retorno NÃO é sobrecarga
public int calcular(int a) { return a; }
public double calcular(int a) { // ERRO! Mesma assinatura
return a;
}
// CORRETO - Sobrecarga
public int calcular(int a) { return a; }
public int calcular(int a, int b) { return a + b; }
// CORRETO - Sobrecarga
public int calcular(int a) { return a; }
public int calcular(String a) { return Integer.parseInt(a); }
4. Polimorfismo de Execução - Sobrescrita (Override)
Sobrescrita ocorre quando uma subclasse redefine um método da classe pai. Decide-se qual método chamar em tempo de execução baseado no tipo real do objeto.
Exemplo Básico:
// Classe Pai
public class Animal {
public void fazer som() {
System.out.println("Som genérico de animal");
}
}
// Subclasse 1
public class Cachorro extends Animal {
@Override
public void fazer som() {
System.out.println("Au au au!");
}
}
// Subclasse 2
public class Gato extends Animal {
@Override
public void fazer som() {
System.out.println("Miau miau!");
}
}
// Subclasse 3
public class Cow extends Animal {
@Override
public void fazer som() {
System.out.println("Muuuuu!");
}
}
// Uso - Polimorfismo em ação!
Animal animal1 = new Cachorro();
Animal animal2 = new Gato();
Animal animal3 = new Cow();
animal1.fazer som(); // Au au au!
animal2.fazer som(); // Miau miau!
animal3.fazer som(); // Muuuuu!
Exemplo Prático: Sistema de Pagamentos
// Classe Pai
public abstract class FormaPagamento {
protected double valor;
public FormaPagamento(double valor) {
this.valor = valor;
}
public abstract double calcularTotal();
public abstract void processar();
}
// Cartão de Crédito
public class CartaoCredito extends FormaPagamento {
private double taxa;
public CartaoCredito(double valor, double taxa) {
super(valor);
this.taxa = taxa;
}
@Override
public double calcularTotal() {
return valor + (valor * taxa / 100); // Adiciona taxa
}
@Override
public void processar() {
System.out.println("Processando cartão de crédito...");
System.out.println("Total: R$ " + String.format("%.2f", calcularTotal()));
}
}
// PIX
public class Pix extends FormaPagamento {
public Pix(double valor) {
super(valor);
}
@Override
public double calcularTotal() {
return valor; // Sem taxa
}
@Override
public void processar() {
System.out.println("Processando PIX...");
System.out.println("Total: R$ " + String.format("%.2f", calcularTotal()));
}
}
// Boleto
public class Boleto extends FormaPagamento {
private double juros;
public Boleto(double valor, double juros) {
super(valor);
this.juros = juros;
}
@Override
public double calcularTotal() {
return valor + juros; // Adiciona juros fixos
}
@Override
public void processar() {
System.out.println("Gerando boleto...");
System.out.println("Total: R$ " + String.format("%.2f", calcularTotal()));
}
}
// Uso
FormaPagamento pagamento1 = new CartaoCredito(100, 3.5);
FormaPagamento pagamento2 = new Pix(100);
FormaPagamento pagamento3 = new Boleto(100, 5);
pagamento1.processar(); // Cartão com 3.5% de taxa
pagamento2.processar(); // PIX sem taxa
pagamento3.processar(); // Boleto com R$ 5 de juros
5. Polimorfismo em Arrays e Collections
Uma das maiores vantagens do polimorfismo é poder trabalhar com múltiplos tipos de forma uniforme:
// Processando diferentes tipos com a mesma interface
FormaPagamento[] pagamentos = new FormaPagamento[3];
pagamentos[0] = new CartaoCredito(100, 3.5);
pagamentos[1] = new Pix(150);
pagamentos[2] = new Boleto(200, 5);
// Loop único - processa todos corretamente
double totalArrecadado = 0;
for (FormaPagamento p : pagamentos) {
p.processar();
totalArrecadado += p.calcularTotal();
}
System.out.println("\nTotal Arrecadado: R$ " +
String.format("%.2f", totalArrecadado));
Com Collections (List):
import java.util.ArrayList;
import java.util.List;
// Polimorfismo com List
List pagamentos = new ArrayList<>();
pagamentos.add(new CartaoCredito(100, 3.5));
pagamentos.add(new Pix(150));
pagamentos.add(new Boleto(200, 5));
pagamentos.add(new Pix(75));
pagamentos.add(new CartaoCredito(50, 2.8));
// Processar todos
double totalRecebido = 0;
for (FormaPagamento p : pagamentos) {
p.processar();
totalRecebido += p.calcularTotal();
}
System.out.println("\n=== RESUMO ===");
System.out.println("Total de Pagamentos: " + pagamentos.size());
System.out.println("Total Recebido: R$ " +
String.format("%.2f", totalRecebido));
6. Casting de Tipos (Conversão)
Às vezes precisamos converter uma referência para tipo mais específico (Downcasting) ou verificar o tipo real:
Upcasting (Automático):
// Upcasting - Conversão automática para classe pai
Cachorro dog = new Cachorro();
Animal animal = dog; // Automático - não precisa cast
// Pode chamar apenas métodos de Animal
animal.fazer som();
Downcasting (Manual):
// Downcasting - Conversão manual para classe filha
Animal animal = new Cachorro();
// ERRADO - Não pode fazer downcasting sem verificar
// Cachorro dog = animal; // ERRO!
// CORRETO - Usar instanceof primeiro
if (animal instanceof Cachorro) {
Cachorro dog = (Cachorro) animal; // Downcasting com cast
dog.trazer(); // Método específico de Cachorro
}
// SEGURO - Java 16+ com Pattern Matching
if (animal instanceof Cachorro dog) {
dog.trazer(); // Já convertido automaticamente
}
Exemplo Prático com instanceof:
List pagamentos = new ArrayList<>();
pagamentos.add(new CartaoCredito(100, 3.5));
pagamentos.add(new Pix(150));
pagamentos.add(new Boleto(200, 5));
// Processamento diferenciado por tipo
for (FormaPagamento p : pagamentos) {
if (p instanceof CartaoCredito) {
CartaoCredito cc = (CartaoCredito) p;
System.out.println("Cartão processado com segurança máxima");
} else if (p instanceof Pix) {
Pix pix = (Pix) p;
System.out.println("PIX processado instantaneamente");
} else if (p instanceof Boleto) {
Boleto boleto = (Boleto) p;
System.out.println("Boleto gerado para envio");
}
}
7. Classes Abstratas e Métodos Abstratos
Classes abstratas forçam subclasses a implementar certos métodos, garantindo polimorfismo correto:
// Classe Abstrata - Não pode ser instanciada
public abstract class Funcionario {
protected String nome;
protected double salarioBase;
public Funcionario(String nome, double salarioBase) {
this.nome = nome;
this.salarioBase = salarioBase;
}
// Método abstrato - deve ser implementado
public abstract double calcularSalario();
// Método concreto
public final void exibir() {
System.out.println("Nome: " + nome);
System.out.println("Salário: R$ " +
String.format("%.2f", calcularSalario()));
}
}
// Subclasse concreta
public class Gerente extends Funcionario {
private double bonus;
public Gerente(String nome, double salarioBase, double bonus) {
super(nome, salarioBase);
this.bonus = bonus;
}
@Override
public double calcularSalario() {
return salarioBase + bonus;
}
}
// Subclasse concreta
public class Desenvolvedor extends Funcionario {
private int anosExperiencia;
public Desenvolvedor(String nome, double salarioBase, int anos) {
super(nome, salarioBase);
this.anosExperiencia = anos;
}
@Override
public double calcularSalario() {
double aumento = (anosExperiencia * 0.05); // 5% por ano
return salarioBase + (salarioBase * aumento);
}
}
// Uso
Funcionario[] funcionarios = new Funcionario[2];
funcionarios[0] = new Gerente("Maria", 5000, 2000);
funcionarios[1] = new Desenvolvedor("João", 4000, 5);
for (Funcionario f : funcionarios) {
f.exibir();
System.out.println();
}
8. Interfaces - Polimorfismo Total
Interfaces definem um contrato que classes devem seguir, permitindo máxima flexibilidade:
// Interface - Contrato
public interface Veiculo {
void ligar();
void desligar();
void acelerar();
}
// Implementação 1
public class Carro implements Veiculo {
@Override
public void ligar() {
System.out.println("Carro ligando: Vruuuum!");
}
@Override
public void desligar() {
System.out.println("Carro desligando");
}
@Override
public void acelerar() {
System.out.println("Carro acelerando: Vrrrrrroooom!");
}
}
// Implementação 2
public class Bicicleta implements Veiculo {
@Override
public void ligar() {
System.out.println("Bicicleta pronta!");
}
@Override
public void desligar() {
System.out.println("Bicicleta parada");
}
@Override
public void acelerar() {
System.out.println("Bicicleta pedalando mais rápido!");
}
}
// Implementação 3
public class Moto implements Veiculo {
@Override
public void ligar() {
System.out.println("Moto ligando: Trrrrr!");
}
@Override
public void desligar() {
System.out.println("Moto desligada");
}
@Override
public void acelerar() {
System.out.println("Moto acelerando: Vrroooom!");
}
}
// Uso
Veiculo[] veiculos = new Veiculo[3];
veiculos[0] = new Carro();
veiculos[1] = new Bicicleta();
veiculos[2] = new Moto();
for (Veiculo v : veiculos) {
v.ligar();
v.acelerar();
v.desligar();
System.out.println();
}
9. Boas Práticas com Polimorfismo
✓ FAÇA:
- Use polimorfismo para escrever código mais flexível
- Use @Override ao sobrescrever métodos
- Prefira trabalhar com tipos pai (interfaces/classes abstratas)
- Use sobrecarga quando o método faz a mesma coisa com tipos diferentes
- Implemente interfaces quando há múltiplos comportamentos
- Use instanceof para downcast seguro
- Documente bem o comportamento esperado de métodos polimórficos
✗ EVITE:
- Sobrecarga com muitas variações (máximo 3-4)
- Mudar significado do método ao sobrescrever
- Downcasting sem verificar tipo primeiro
- Criar métodos com nomes semelhantes para confundir
- Usar polimorfismo quando a lógica é muito diferente
- Sobrescrever métodos finais
10. Erros Comuns com Polimorfismo
❌ Erro 1: ClassCastException
// ERRADO
Animal animal = new Gato();
Cachorro dog = (Cachorro) animal; // ClassCastException!
// CORRETO
Animal animal = new Gato();
if (animal instanceof Cachorro) {
Cachorro dog = (Cachorro) animal;
} else if (animal instanceof Gato) {
Gato gato = (Gato) animal;
}
❌ Erro 2: Não usar @Override
public class Animal {
public void fazer som() {
System.out.println("Som genérico");
}
}
// RUIM - Sem @Override
public class Cachorro extends Animal {
public void fazerSom() { // ERRO! Digitação
System.out.println("Au au!");
}
}
// BOM - Com @Override (erro seria detectado)
public class Cachorro extends Animal {
@Override
public void fazer som() {
System.out.println("Au au!");
}
}
❌ Erro 3: Sobrecarga conflitante
// ERRADO - Ambíguo
public class Processar {
public void executar(int valor) { }
public void executar(Integer valor) { } // Confunde!
}
// Chamada ambígua
Processar p = new Processar();
p.executar(5); // Qual método chama?
// CORRETO - Claro
public class Processar {
public void executarInteiro(int valor) { }
public void executarObjeto(Integer valor) { }
}
❌ Erro 4: Mudar assinatura na subclasse
// ERRADO - Mudou parâmetros
public class Animal {
public void fazer som() { }
}
public class Cachorro extends Animal {
@Override
public void fazer som(String tipo) { // ERRO! Assinatura diferente!
}
}
// CORRETO - Mantém assinatura
public class Cachorro extends Animal {
@Override
public void fazer som() {
System.out.println("Au au!");
}
}
11. Comparação - Sobrecarga vs Sobrescrita
| Aspecto | Sobrecarga (Overload) | Sobrescrita (Override) |
|---|---|---|
| Tipo | Polimorfismo de Compilação | Polimorfismo de Execução |
| Nome | Mesmo nome | Mesmo nome |
| Assinatura | Diferente (parâmetros) | Igual |
| Classe | Mesma classe ou herança | Classe filha |
| Decisão | Tempo de compilação | Tempo de execução |
| @Override | Não necessário | Recomendado |
| Exemplo | soma(int, int) e soma(int, int, int) | fazer som() em Cachorro e Gato |
12. Exercícios Práticos
Exercício 1: Sistema de Formas
Crie interface Forma com método calcularArea(). Implemente em Circulo, Quadrado e Triangulo. Use polimorfismo para calcular área total.
Exercício 2: Operações Matemáticas
Crie classe com métodos sobrecarregados somar() que aceita: int+int, double+double, String+String (concatenação), e int+double.
Exercício 3: Sistema de Aparelhos
Crie classe abstrata Eletronico com método funcionar(). Implemente em TV, Microondas, Geladeira. Use polimorfismo.
Exercício 4: Gestor de Documentos
Crie interface Documento com método visualizar(). Implemente em PDF, Word, Imagem. Processe array de documentos.
13. Resumo - Conceitos Principais
Polimorfismo
Capacidade de um objeto assumir múltiplas formas ou um método ter múltiplos comportamentos.
Sobrecarga (Overload)
Múltiplos métodos com mesmo nome mas parâmetros diferentes. Decidido em tempo de compilação.
Sobrescrita (Override)
Subclasse redefine método da classe pai. Decidido em tempo de execução. Use @Override.
Upcasting
Conversão automática de tipo específico para tipo mais geral (subclasse → superclasse).
Downcasting
Conversão manual de tipo geral para tipo específico. Use instanceof primeiro.
Classes Abstratas
Definem contrato que subclasses devem seguir. Não podem ser instanciadas.
Interfaces
Permitem máxima flexibilidade. Classes podem implementar múltiplas interfaces.
Conclusão
Polimorfismo é o que torna o código orientado a objetos realmente poderoso e flexível. Permite escrever código que funciona com múltiplos tipos de forma elegante.
Os três pilares trabalham juntos:
- Encapsulamento: Protege dados
- Herança: Reutiliza código
- Polimorfismo: Oferece flexibilidade
Próximo passo: Estude Abstração - o quarto pilar - para completar sua compreensão de POO!