Polimorfismo, Binding e Generics

Aula 7 — Programação Orientada a Objetos

Autor

Marcos M. Raimundo — Instituto de Computação, UNICAMP

Data de Publicação

30 de agosto de 2026

Slides Lista de aulas

1 Proposta da Aula

A Aula 6 nos deu o contrato (Pagavel) e o vocabulário de defesa (exceções). Falta explicar como o compilador aceita que a mesma variável Pagavel forma receba, em momentos diferentes, um Pix ou um Cartao — duas classes sem nada em comum em dados. A resposta é o Polimorfismo.

O roteiro, em quatro perguntas:

  1. Quem decide qual código realmente roda quando chamamos forma.criarCobranca() — o compilador ou a máquina virtual?
  2. Existe só um tipo de polimorfismo, ou a sobrecarga de métodos conta como um também?
  3. Como o polimorfismo elimina, de vez, as longas cadeias de if/else baseadas em tipo?
  4. Por que uma lista “aceita-tudo” é um risco, e como Generics resolve isso sem perder flexibilidade?

2 Polimorfismo e o Mecanismo de Late Binding

Polimorfismo (do grego, “muitas formas”): a capacidade de uma variável ou método assumir diferentes comportamentos dependendo do objeto real que manipula. Se a interface deu o contrato, o polimorfismo é o mecanismo que permite que diferentes classes cumpram esse contrato de maneiras distintas, sem que o sistema precise ser reescrito para cada uma.

O mecanismo central é o Late Binding (ou Dynamic Dispatch). Ao declarar Pagavel forma, o compilador faz uma checagem estática: só garante que criarCobranca() existe na interface Pagavel. Ele não sabe — e não precisa saber — qual código roda de fato. Essa decisão só acontece em tempo de execução: a JVM olha o objeto real na Heap e desvia o fluxo para a implementação certa.

List<Pagavel> opcoes = clienteLogado.consultaMeiosPagamento();
Pagavel forma = opcoes.get(escolha - 1); // Pix, Boleto ou Cartao — tanto faz
String id = forma.criarCobranca(total);  // o comportamento real emerge em RUNTIME

O que praticamos aqui é o Polimorfismo de Inclusão: um subtipo (Pix) pode ser usado em qualquer lugar em que seu supertipo (Pagavel) é esperado. Se adicionarmos Cripto implements Pagavel amanhã, nenhuma linha do controlador muda — o sistema fica “Aberto para Extensão, Fechado para Modificação” (OCP).

3 A Taxonomia de Cardelli-Wegner

Polimorfismo não é um conceito único. Cardelli e Wegner organizam-no em dois eixos:

Universal — funciona para um número infinito de tipos:

  • Inclusão (Subtipagem): o pilar da OO — qualquer classe que implemente Pagavel serve.
  • Paramétrico (Generics): List<T> funciona da mesma forma para Produto ou Cliente, sem exigir hierarquia de herança.

Ad-hoc — funciona só para um conjunto finito e específico de tipos, resolvido cedo pelo compilador:

  • Sobrecarga (Overloading):
public class Notificador {
    public void enviar(String email) { ... }                  // versao 1
    public void enviar(String email, String assunto) { ... }   // versao 2 (sobrecarga)
}

Crítica arquitetural: a sobrecarga é um “polimorfismo aparente”. Se surgir um novo tipo de contato, é preciso voltar a Notificador e escrever uma nova versão — não resolve extensibilidade, viola o OCP.

  • Coerção: conversão implícita feita pela linguagem —
double preco = 100;       // int vira double
double total = preco + 5; // literal 5 vira double
String msg = "Total: " + total; // double vira String

É um comportamento embutido e rígido, sem troca de comportamento baseada em lógica de negócio.

A tabela que resume tudo:

Característica Static (Early) Binding Dynamic (Late) Binding
Quando ocorre Tempo de compilação Tempo de execução
Quem decide O compilador (tipo da variável) A JVM (objeto real na memória)
Exemplos Sobrecarga, Coerção, métodos static/private Polimorfismo de Inclusão (interfaces, herança)
Vantagem Performance levemente superior Desacoplamento total e extensibilidade

A OO aposta no Dynamic Binding porque é ele que permite trocar o “motor” de uma aplicação inteira — alterando uma linha de configuração — sem que o código cliente jamais saiba da mudança.

3.1 Taxonomia do Polimorfismo

DicaPor que a sobrecarga de métodos é chamada de “polimorfismo aparente”, mesmo permitindo múltiplas versões do mesmo nome de método?

Escreva sua resposta e compare com um colega antes de avançar (2 min).

  • □ O polimorfismo ad-hoc funciona apenas sobre um conjunto finito e delimitado de tipos.
  • □ A sobrecarga (overloading) é resolvida pelo compilador em tempo de compilação, com base nos argumentos passados.
  • □ O polimorfismo de inclusão é resolvido em tempo de execução, olhando o objeto real na memória.
  • □ A sobrecarga de métodos resolve completamente o problema da extensibilidade prevista pelo OCP.

4 O Fim do “Mar de IFs” e o Princípio Aberto/Fechado

Sem polimorfismo, cada novo tipo de pagamento exige mais um else if:

// Codigo SEM polimorfismo: rigido e fragil
public void processarPagamento(String tipo, double valor) {
    if (tipo.equals("PIX")) {
        pixService.gerarQR(valor);
    } else if (tipo.equals("BOLETO")) {
        boletoService.gerarLinhaDigitavel(valor);
    } else if (tipo.equals("CARTAO")) {
        cartaoService.autorizar(valor);
    }
    // Toda vez que um novo metodo surge, voce precisa "abrir" este codigo!
}

Com polimorfismo, o fluxo vira uma linha:

// Nao importa o que 'formaEscolhida' e, desde que seja Pagavel
String id = formaEscolhida.criarCobranca(total);

Se CriptoPayment implements Pagavel surgir amanhã, esse código não muda. É exatamente o Princípio Aberto/Fechado (OCP): entidades de software devem estar abertas para extensão (novas classes) e fechadas para modificação (o código que já funciona continua intocado).

Conceito Aberto para Extensão Fechado para Modificação
Aplicação Nova classe implementa Pagavel Checkout não precisa ser alterado
Mecanismo Novas classes concretas Programação para interfaces (Dynamic Binding)

A meta-regra prática: se você precisa de um if (ou switch, ou instanceof) para verificar o tipo de um objeto de negócio, você provavelmente está violando o OCP e perdendo a chance de deixar o polimorfismo fazer esse trabalho.

4.1 OCP e Substitutibilidade

DicaSe amanhã surgir um novo meio de pagamento, o que exatamente muda no CheckoutController — e o que isso diz sobre o OCP?

Escreva sua resposta e compare com um colega antes de avançar (2 min).

  • □ Adicionar Cripto implements Pagavel exige alterar o código do CheckoutController.
  • □ “Aberto para extensão” significa que novos comportamentos entram por novas classes, não por edição das antigas.
  • □ Precisar de instanceof para decidir o que fazer com um objeto de negócio é sinal de que o polimorfismo não foi bem aproveitado.
  • □ A substitutibilidade é o que permite ao CheckoutController tratar Pix, Boleto e Cripto de forma idêntica.

5 Generics: da Lista “Aceita-Tudo” à Etiqueta de Tipo

Antes do Java 5, uma coleção era um saco de Object — qualquer coisa podia entrar:

// Sem Generics: nada impede um erro de lógica
opcoesReais.add("Nao sou um pagamento"); // compila! Bomba-relogio.

O problema é a detecção tardia: o erro de lógica acontece na inserção, mas o programa só “explode” muito depois, quando o código tenta usar o objeto como se fosse Pagavel — um ClassCastException em tempo de execução, longe da causa real.

Generics resolvem isso com uma “etiqueta de tipo”:

// O compilador garante que esta lista SO tera Pagaveis
List<Pagavel> opcoesReais = clienteLogado.consultaMeiosPagamento();
// opcoesReais.add("Nao sou um pagamento"); // ERRO DE COMPILACAO, agora!

Quatro vantagens práticas:

  1. Robustez: move a detecção de erro do tempo de execução para o tempo de compilação.
  2. Legibilidade: List<Pagavel> já diz muito mais do que List sozinho.
  3. Reutilização: algoritmos genéricos continuam seguros para qualquer tipo (uma lista que ordena qualquer Comparable, por exemplo).
  4. Performance: evita casts repetidos e desnecessários no código.

Generics são, na prática, o Polimorfismo Paramétrico da taxonomia de Cardelli-Wegner em ação: o mesmo algoritmo de lista funciona para Pagavel, Produto ou Cliente, sem que nenhuma dessas classes precise pertencer à mesma hierarquia de herança.

5.1 Generics e o Fim do Mar de IFs

DicaGenerics é uma instância de qual tipo de polimorfismo na taxonomia de Cardelli-Wegner — e por que ele não exige que os tipos usados pertençam à mesma hierarquia de herança?

Escreva sua resposta e compare com um colega antes de avançar (2 min).

  • □ Sem Generics, um erro de tipo numa coleção só é detectado quando o item incompatível é efetivamente usado, não quando é inserido.
  • List<Pagavel> impede, em tempo de compilação, que um objeto incompatível seja adicionado à lista.
  • □ Generics é uma forma de Polimorfismo Ad-hoc, resolvida da mesma forma que a sobrecarga de métodos.
  • □ Precisar de um if para verificar o tipo concreto de um objeto de negócio é um sintoma de violação do OCP.

6 Conclusão

Voltando às quatro perguntas da abertura:

  1. Quem decide o código real: a JVM, em tempo de execução, olhando o objeto na Heap — não o compilador.
  2. Um só tipo de polimorfismo? Não — Universal (Inclusão, Paramétrico) e Ad-hoc (Sobrecarga, Coerção), com garantias bem diferentes de extensibilidade.
  3. O fim do “Mar de IFs”: delegar a decisão para o próprio objeto, em vez de perguntar “qual é o seu tipo?” a cada chamada.
  4. A lista “aceita-tudo”: Generics move o erro do tempo de execução para o tempo de compilação, sem custo de flexibilidade.

Ponte para a Aula 8

Interfaces e Polimorfismo de Inclusão resolveram contratos entre classes sem parentesco algum. Existe outro mecanismo de polimorfismo — mais antigo, mais poderoso e mais perigoso — que compartilha estrutura, não só comportamento: a Herança. A Aula 8 explora esse mecanismo e o Princípio da Substituição de Liskov, a regra que decide quando herdar é seguro.

Exercícios Soluções