Polimorfismo
Polimorfismo é a capacidade de uma variável assumir o papel de diferentes tipos de objectos que se relacionem entre si através de uma classe base, oferecendo assim a possibilidade de redefinir o comportamento herdado sem alterar a interface de comunicação.
Tentando transpor a teoria para um exemplo, se considerarmos que Joao é subclasse de Pessoa então um objecto do tipo Pessoa consegue guardar um objecto do tipo Joao. Desta forma, uma variável que esteja definida como sendo do tipo Pessoa consegue guardar objectos do tipo Pessoa, Joao. De outra forma, uma variável consegue, através de polimorfismo, guardar objectos do seu tipo e de qualquer tipo descendente directo ou indirecto (netos, bisnetos, etc).
Ao usarmos polimorfismo podemos cair em situações onde não sabemos, como programadores, qual será o tipo de dados exacto que está guardado na variável, isso pode simplesmente não nos interessar se estivermos a aceder a métodos que existam em todos os objectos de determinada hierarquia, mas é necessário que o compilador e o interpretador consigam saber com exactidão quais os objectos que estão a ser manipulados.
Para esse efeito existem dois tipos de detecção:
- Static binding, binding estático, em que o compilador determina, durante o processo de compilação, qual o tipo de objecto e que método deve ser invocado.
- Dynamic binding, binding dinâmico, em que cabe ao interpretador determinar, durante a execução do programa, qual o tipo de objecto e que método invocar. Este processo aplica uma penalização de performance a todas as linguagens orientadas a objectos.
Caso seja necessário determinar o tipo de dados de forma programática, é possível usar o operador instanceof para determinar qual o tipo de dados, com alguma certeza, que está a ser manipulado. A exactidão da verificação dependerá das classe envolvidas. Por exemplo, assumindo a hierarquia SerVivo > Mamifero > Humano e SerVivo > Mamifero > Golfinho, vamos aplicar o operador algumas vezes
SerVivo ser;
Humano maria;
Golfinho saltitao;
ser instanceof Humano > false
ser instanceof SerVivo > true
maria instanceof SerVivo > true
maria instanceof Mamifero > true
maria instanceof Humano > true
saltitao instanceof Humano > false
saltitao instanceof SerVivo > true
Se verificarmos, a variável saltitao vai responder afirmativamente quando perguntamos se é um SerVivo ou se é um Mamifero, tal como a variável maria. Tendo uma variável que responda afirmativamente nestes dois casos não conseguimos saber se, além de SerVivo e Mamifero, ela é outra coisa qualquer, a não ser que experimentemos todas as combinações possíveis.
Usar o instanceof é, também, um processo muito lento e que deve ser usado quando não existem alternativas, por exemplo, ler objectos de um ficheiro que não controlamos ou filtrar uma lista de objectos que contém objectos misturados e que pretendemos separar.
Limitar polimorfismo
Como o processo de binding dinâmico é um processo dispendioso, não faz sentido usá-lo em situações onde sabemos, exactamente, que tipo de dados vai ser usado. Assim, se temos uma classe que sabemos não tem subclasses, nem irá ter, podemos aplicar o modificador final. Este modificador permite indicar que uma classe não vai usar binding dinâmico para a determinação do seu tipo. Do mesmo modo, podemos aplicar o modificador a métodos, impedindo que os métodos sejam redefinidos e que seja necessário efectuar uma pesquisa para determinar qual o método correcto a invocar.
A utilização do modificador final permite ao compilador e interpretador a execução de algumas optimizações para tornar a execução da nossa aplicação mais rápida.