Polimorfismo, Binding e Generics
Aula 7 — Programação Orientada a Objetos
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:
- Quem decide qual código realmente roda quando chamamos
forma.criarCobranca()— o compilador ou a máquina virtual? - Existe só um tipo de polimorfismo, ou a sobrecarga de métodos conta como um também?
- Como o polimorfismo elimina, de vez, as longas cadeias de
if/elsebaseadas em tipo? - 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 RUNTIMEO 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
Pagavelserve. - Paramétrico (Generics):
List<T>funciona da mesma forma paraProdutoouCliente, 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
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
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 Pagavelexige alterar o código doCheckoutController. - □ “Aberto para extensão” significa que novos comportamentos entram por novas classes, não por edição das antigas.
- □ Precisar de
instanceofpara 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
CheckoutControllertratarPix,BoletoeCriptode 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:
- Robustez: move a detecção de erro do tempo de execução para o tempo de compilação.
- Legibilidade:
List<Pagavel>já diz muito mais do queListsozinho. - Reutilização: algoritmos genéricos continuam seguros para qualquer tipo (uma lista que ordena qualquer
Comparable, por exemplo). - 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
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
ifpara 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:
- Quem decide o código real: a JVM, em tempo de execução, olhando o objeto na Heap — não o compilador.
- Um só tipo de polimorfismo? Não — Universal (Inclusão, Paramétrico) e Ad-hoc (Sobrecarga, Coerção), com garantias bem diferentes de extensibilidade.
- 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.
- 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
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?
Um desenvolvedor júnior escreve um método
efetuarCobrancaque inspeciona umaString tipoPagamentonuma cadeia deif/elsepara dispararPixServiceouBoletoService. Critique esse design com base no Princípio Aberto/Fechado e mostre, em texto, como o Polimorfismo de Inclusão elimina esse sintoma.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 umClassCastExceptionem 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).