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.

7 Exercícios

7.1 Questões discursivas

  1. Defina Static (Early) Binding e Dynamic (Late) Binding, explicitando em qual momento do ciclo de vida do software cada um atua e qual agente (compilador ou JVM) toma a decisão. Por que o desacoplamento em Orientação a Objetos depende criticamente do Late Binding?

  2. Um desenvolvedor júnior escreve um método efetuarCobranca que inspeciona uma String tipoPagamento numa cadeia de if/else para disparar PixService ou BoletoService. Critique esse design com base no Princípio Aberto/Fechado e mostre, em texto, como o Polimorfismo de Inclusão elimina esse sintoma.

  3. Explique o cenário de “Heterogeneidade Acidental” numa lista sem Generics num sistema de checkout, e como List<Pagavel> protege o usuário final de um ClassCastException em produção.

7.2 Questões de Verdadeiro/Falso

Cada bloco de 4 itens trata do mesmo tema. A questão só é considerada correta se todos os 4 itens forem julgados corretamente (deixar em branco tem penalidade de 20% da nota da questão).

NotaO que é Polimorfismo
  • □ Duas classes que implementam métodos com o mesmo nome, mas sem relação de herança ou interface comum entre elas, já caracterizam polimorfismo, pois cada uma se comporta de forma diferente.
  • □ O objetivo prático do polimorfismo é reduzir o acoplamento e aumentar a extensibilidade do sistema.
  • pagamentoConfirmado() pode se comportar como consulta de API no Pix e como verificação de data no Boleto.
  • □ O código que chama a operação precisa conhecer os detalhes de implementação de quem a executa.
NotaLate Binding (Dynamic Dispatch)
  • □ Se a interface Pagavel não declarasse criarCobranca(), mas todas as classes que a implementam (Pix, Cartao) declarassem esse método por conta própria, a chamada forma.criarCobranca(total) ainda compilaria normalmente.
  • □ Num sistema de plugins em que cada plugin implementa a interface comum Processador, é possível adicionar um plugin novo, compilado separadamente, sem recompilar o núcleo do sistema — graças ao mesmo mecanismo que decide, em Pagavel, qual criarCobranca() roda.
  • □ A JVM decide olhando o tipo declarado da variável, não o objeto real na memória.
  • □ Late Binding é o que permite trocar a implementação injetada sem que o código cliente perceba.
NotaPolimorfismo Universal
  • □ Uma nova classe que implemente Pagavel depois que o sistema já estiver em produção pode ser usada em qualquer lugar que espere um Pagavel, sem exigir recompilação do código cliente existente.
  • □ O Polimorfismo Paramétrico exige que todos os tipos usados pertençam à mesma hierarquia de herança.
  • □ Um método genérico ordenar(List<T> lista) que funciona tanto para List<Produto> quanto para List<Cliente> deixaria de compilar se Produto e Cliente não implementassem uma interface Comparable em comum.
  • □ Um sistema que usa apenas Polimorfismo Ad-hoc (sobrecarga e coerção) já suporta, em princípio, um número infinito de tipos diferentes sem precisar editar código existente.
NotaPolimorfismo Ad-hoc
  • □ Um método enviar(String email) e um método enviar(int codigo) na mesma classe Notificador configuram sobrecarga, mesmo que um deles nunca seja chamado em nenhum lugar do sistema.
  • □ A sobrecarga é considerada um polimorfismo verdadeiramente dinâmico e extensível.
  • □ Se dois métodos sobrecarregados enviar(String email) e enviar(Object dado) puderem ambos aceitar um argumento do tipo String, o compilador escolhe sempre a versão mais genérica (Object), para reduzir o número de conversões implícitas.
  • □ A coerção de int para double numa expressão aritmética pode ser desativada em tempo de execução dependendo do valor concreto da variável, da mesma forma que o Late Binding decide em runtime qual método roda.
NotaBinding Estático vs. Dinâmico
  • □ Um IDE que sugere automaticamente, enquanto o código é digitado, qual método enviar() sobrecarregado será chamado, está inspecionando o comportamento do Static Binding, não do Dynamic Binding.
  • □ Métodos static e private em Java são resolvidos via Dynamic Binding.
  • □ O Dynamic Binding é o que sustenta o desacoplamento e a extensibilidade típicos da Orientação a Objetos.
  • □ A Injeção de Dependência se apoia no Dynamic Binding para trocar implementações sem alterar o código cliente.
NotaO Fim do “Mar de IFs”
  • □ Uma cadeia de if/else que verifica, dentro de um método de log genérico, se o parâmetro é null antes de gravá-lo, é um sintoma da mesma violação de OCP que a cadeia de if/else sobre tipos de pagamento.
  • □ Com polimorfismo, adicionar um novo meio de pagamento não exige alterar o código do controlador.
  • □ O uso de instanceof para decidir o comportamento com base no tipo concreto é incentivado pelo polimorfismo.
  • □ Delegar a decisão para o próprio objeto substitui a necessidade de perguntar “qual é o seu tipo?”.
NotaO Princípio Aberto/Fechado (OCP)
  • □ Um sistema está “aberto para extensão” quando comportamentos novos entram via novas classes, não via edição das antigas.
  • □ Estar “fechado para modificação” significa que o código cliente não precisa ser retestado ao aceitar um novo ator.
  • □ Programar para interfaces, em vez de classes concretas, é o mecanismo que viabiliza o cumprimento do OCP.
  • □ O OCP recomenda revisar e reescrever o código cliente a cada novo comportamento adicionado ao sistema.
NotaSubstitutibilidade
  • □ A substitutibilidade é a capacidade de trocar implementações concretas por uma abstração comum sem impacto perceptível.
  • CheckoutController trata Pix, Boleto e Cartao de forma uniforme através da “lente” do tipo Pagavel.
  • □ Para usar criarCobranca(), o controlador precisa saber qual classe concreta está por trás da referência.
  • □ A substitutibilidade é o que permite ao sistema aceitar novos meios de pagamento sem alterar o núcleo.
NotaO Problema dos Raw Types
  • □ Sem Generics, uma coleção Java podia armazenar referências genéricas do tipo Object.
  • □ O risco de inserir um objeto incompatível numa lista sem Generics é detectado imediatamente pelo compilador.
  • □ Recuperar dados de uma coleção não parametrizada exigia casting manual, sujeito a ClassCastException.
  • □ A detecção tardia de um erro de tipo é o principal risco prático dos raw types.
NotaGenerics como Etiqueta de Segurança
  • List<Pagavel> desloca a detecção de erros de tipo do tempo de execução para o tempo de compilação.
  • □ Generics elimina a necessidade de casting manual na leitura de itens de uma coleção parametrizada.
  • □ Generics reduz a legibilidade do código, pois exige mais caracteres na declaração de tipos.
  • □ Proibir a inserção de tipos incompatíveis é uma das formas como Generics aumenta a robustez do sistema.
NotaPolimorfismo Paramétrico e Identidade
  • □ O Polimorfismo Paramétrico usa variáveis de tipo (como <T>) mantendo a segurança de tipos estáticos.
  • □ Diferente do Polimorfismo de Inclusão, o Paramétrico não exige que os tipos usados pertençam à mesma árvore de herança.
  • □ Um Repositorio<T> genérico centraliza a lógica de infraestrutura sem duplicar código para cada entidade.
  • □ Recuperar um item de um Repositorio<T> obriga o desenvolvedor a fazer um cast manual para o tipo concreto.
NotaSíntese: Contrato, Polimorfismo e Segurança de Tipo
  • □ Interfaces definem o contrato; Polimorfismo de Inclusão o torna substituível; Generics protege coleções desses contratos.
  • □ O uso conjunto de interfaces, polimorfismo e Generics é o que sustenta sistemas verdadeiramente Plug-and-Play.
  • □ Um sistema bem desenhado com essas três ferramentas ainda pode exigir ifs de tipo em pontos centrais do fluxo.
  • □ A meta comum às três ferramentas é mover decisões e erros para o momento mais cedo e mais seguro possível do ciclo de vida do software.