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.
Escreva sua resposta e compare com um colega antes de avançar (2 min).
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.
CheckoutController — e o que isso diz sobre o OCP?
Escreva sua resposta e compare com um colega antes de avançar (2 min).
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.
Escreva sua resposta e compare com um colega antes de avançar (2 min).
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).
- ( ) Polimorfismo é a capacidade de uma variável ou método assumir comportamentos diferentes conforme o objeto real manipulado.
- ( ) O objetivo prático do polimorfismo é reduzir o acoplamento e aumentar a extensibilidade do sistema.
- ( )
pagamentoConfirmado()pode se comportar como consulta de API noPixe como verificação de data noBoleto. - ( ) O código que chama a operação precisa conhecer os detalhes de implementação de quem a executa.
- ( ) O compilador, ao ver
Pagavel forma, verifica apenas que o método chamado existe na interface. - ( ) A decisão de qual implementação executar ocorre em tempo de execução, pela JVM.
- ( ) 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.
- ( ) O Polimorfismo de Inclusão permite que qualquer implementação de uma interface seja usada no lugar do tipo mais genérico.
- ( ) O Polimorfismo Paramétrico exige que todos os tipos usados pertençam à mesma hierarquia de herança.
- ( )
List<T>funciona da mesma forma paraProdutoouCliente, sem exigir parentesco entre as classes. - ( ) O Polimorfismo Universal é chamado assim porque funciona sobre um número, em princípio, infinito de tipos.
- ( ) A sobrecarga (overloading) permite vários métodos com o mesmo nome e assinaturas distintas na mesma classe.
- ( ) A sobrecarga é considerada um polimorfismo verdadeiramente dinâmico e extensível.
- ( ) A escolha de qual método sobrecarregado roda é feita pelo compilador, com base nos tipos dos argumentos.
- ( ) A coerção é a conversão implícita de tipos, como
intparadouble, embutida na linguagem.
- ( ) O Static Binding ocorre em tempo de compilação; o Dynamic Binding, em tempo de execução.
- ( ) Métodos
staticeprivateem 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.
- ( ) Uma cadeia de
if/elseverificando o tipo de um objeto de negócio é um sintoma clássico de violação do OCP. - ( ) Com polimorfismo, adicionar um novo meio de pagamento não exige alterar o código do controlador.
- ( ) O uso de
instanceofpara 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?”.
- ( ) 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.
- ( ) A substitutibilidade é a capacidade de trocar implementações concretas por uma abstração comum sem impacto perceptível.
- ( )
CheckoutControllertrataPix,BoletoeCartaode forma uniforme através da “lente” do tipoPagavel. - ( ) 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.
- ( ) 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.
- ( )
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.
- ( ) 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.
- ( ) 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.