JAVA Learning

Hub de Conteúdo

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

java
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

java
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

java
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:

java
// 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:

java
// 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

java
// 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:

java
// 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):

java
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):

java
// 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):

java
// 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:

java
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:

java
// 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:

java
// 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

java
// 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

java
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

java
// 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

java
// 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!