Existem várias classificações e tipos de polimorfismo. C++ apresenta vários tipos de polimorfismo . Java apresenta um conjunto mais reduzido evitando principalmente polimorfismos ad-hoc.
Polimorfismo, do grego: muitas formas. Polimorfismo é a
capacidade de um operador executar a ação apropriada
dependendo do tipo do operando. Aqui operando e operador estão
definidos num sentido mais geral: operando pode significar argumentos
atuais de um procedimento e operador o procedimento, operando
pode significar um objeto e operador um método, operando
pode significar um tipo e operador um objeto deste tipo.
Este exemplo já foi apresentado em 2.1.3. Também trata-se de um polimorfismo, pode ser classificado como polimorfismo de inclusão. Um método é uma redefinição de um método herdado, quando está definido em uma classe construída através de herança e possui o mesmo nome, valor de retorno e argumentos de um método herdado da classe pai. A assinatura do método tem que ser idêntica, ou seja, teremos redefinição quando uma classe filha fornece apenas uma nova implementação para o método herdado e não um novo método.
Se a classe filha fornecer um método de cabeçalho
ou assinatura parecida com a do método herdado (difere
ou no número ou no tipo dos argumentos, ou então
no tipo do valor de retorno) então não se trata
mais de redefinição, trata-se de uma sobrecarga,
pois criou-se um novo método. Uma chamada ao método
herdado não mais é interceptada por esse novo método
de mesmo nome. O método tem o mesmo nome, mas é
ligeiramente diferente na sua assinatura (o corpo ou bloco de
código {} não importa), o que já implica
que não proporciona o mesmo comportamento (behaviour) do
método da superclasse.
Este tipo de polimorfismo permite a existência de vários
métodos de mesmo nome, porém com assinaturas levemente
diferentes ou seja variando no número e tipo de argumentos
e no valor de retorno. Ficaria a cargo do compilador escolher
de acordo com as listas de argumentos os procedimentos ou métodos
a serem executados.
No exemplo a seguir vamos sobrecarregar o construtor de uma classe, esta classe passará a ter duas versões de construtores, vale lembrar que assim como o construtor será sobrecarregado, qualquer outro método poderia ser. O compilador saberá qual método chamar não mais pelo nome, mas pelos argumentos.
O método Ponto(Ponto ap); é um "copy constructor", pois tem o mesmo nome que Ponto(float dx,float dy);. Tal duplicação de nomes pode parecer estranha, porém Java permite que eles coexistam para uma mesma classe porque não tem a mesma assinatura (nome+argumentos). Isto se chama sobrecarga de método, o compilador sabe distinguir entre esses dois construtores. Outros métodos, não só construtores poderão ser sobrecarregados para vários argumentos diferentes, esse recurso é um polimorfismo do tipo "ad-hoc".
O que é interessante para nós é o fato de o argumento do construtor Ponto(Ponto ap); ser da mesma classe para qual o construtor foi implementado, o que caracteriza um "copy constructor" é que inicializa um objeto a partir de outro da mesma classe. Outros métodos semelhantes seriam: Circulo(Circulo a); Mouse(Mouse d); . Implementar copy constructor pode ser muito importante, lembre-se dos problemas com cópias de objetos apresentados em 1.3.3.
Por questões de espaço, basearemos nosso exemplo
no tipo abstrato de dados fração, apresentado em
1.6.1. Você deve modificar a classe Fracao para que ela
tenha dois construtores, o que esta em negrito deverá ser
acrescentado ao código original:
public Fracao(int umso) //sobrecarga do construtor original
{
num=umso;
den=1; //subentendido
}
public Fracao(Fracao copieme) //esse e' um copy constructor
e uma sobrecarga
{
num=copieme.retorna_num();
dem=copieme.retorna_den();
}
public Fracao(int t,int m) //construtor original
{
num=t;
den=m;
this.simplifica(); //chamada para o mesmo objeto.
}
//O programa principal, sobre a modificacao em negrito
class Principal {
public static void main(String args[])
{
Fracao a,b,c;
a=new Fracao(5); //elimine o segundo argumento
b=new Fracao(2,6);
System.out.print("Esta e' a fracao a: ");
a.mostra();
System.out.print("Esta e' a fracao b: ");
b.mostra();
c=a.soma(b);
System.out.print( "c de a+b: "); //c(a+b)
c.mostra();
System.out.print("a*b: ");
c=a.multiplicacao(b);
c.mostra();
System.out.print("a+b: ");
c=a.soma(b);
c.mostra();
System.out.print("a>=b: ");
System.out.println(a.maiorouigual(b));
System.out.print("a==b: ");
System.out.println(a.igual(b));
System.out.print("a!=b: ");
System.out.println(a.diferente(b));
System.out.print("(int)a ");
System.out.println(a.converteint());
System.out.print("(double)a ");
System.out.println( a.convertedbl());
}
}
Esta e' a fracao a: (5/1) Esta e' a fracao b: (1/3) c de a+b: (16/3) a*b: (5/3) a+b: (16/3) a>=b: true a==b: false a!=b: true (int)a 5 (double)a 5
Teste o "copy constructor" para o tipo abstrato de dados fração apresentado acima. Quando um só número for passado para o construtor desta classe, subentende-se que o construtor chamado é o de um só argumento inteiro e que portanto o denominador será igual a 1.
Agora vamos falar do "copy constructor", que embora implementado, não foi testado em main() . Esse método, pertence a outro objeto que não o argumento copieme, então para distinguir o atributo num deste objeto, do atributo num de copieme usamos copieme.num e simplesmente num para o objeto local, objeto em questão, ou objeto dono do método chamado.
Exercícios:
1-
Faça um "copy constructor" para uma das classes já implementadas neste texto.
2-
Sobrecarregue o método move da classe Ponto para aceitar um Ponto como argumento, subentende-se que devemos mover a distância x e a distância y daquele ponto a origem.
3-
Crie um método de nome unitarizado para a classe Ponto.
Este método deve interpretar o Ponto como um vetor e retornar
um novo Ponto que contém as coordenadas do vetor unitarizado.
Unitarizar é dividir cada coordenada pelo módulo
do vetor. O módulo é a raiz quadrada da soma dos
quadrados das componentes.
Java não fornece recursos para sobrecarga de operador, o que é perfeitamente condizente com a filosofia da linguagem. Seus criadores que acreditavam que a linguagem deveria ser pequena, simples, segura de se programar e de se usar (simple, small, safe and secure).
A ausência de sobrecarga de operadores pode ser contornada
definindo apropriadamente classes e métodos.
Em um dos exercícios anteriores (no tópico sobre herança) pedíamos que você definisse uma hierarquia composta de três classes. A classe pai tinha o nome de Forma, e as classes herdeiras desta eram Ponto e Retangulo. Embora a classe forma não possuísse sentido prático, ela permitia certas operações como move, altera_x(int nx), entre outras (retorne a este exercício).
Na verdade o que desejávamos era que esta classe Forma se comportasse como um esqueleto para as suas classes filhas, nós não queríamos instanciá-la. Classes abstratas permitem exatamente isto pois não podem ser instanciadas embora possam ser usadas de outras maneiras.
Classes abstratas são poderosas, elas permitem: criação de listas heterogêneas, ocorrência de "dynamic binding" e maior clareza no projeto de sistemas. Os packages que vem com a linguagem estão repletos de exemplos de classes abstratas.
Métodos abstratos, obrigatoriamente pertencem a classes abstratas, e são métodos desprovidos de implementação, são apenas definições que serão aproveitadas por outras classes da hierarquia. Voltando ao exemplo da hierarquia Forma, Ponto e Retangulo. O método mostra poderia ter sido definido na classe base abstrata (Forma) como um método abstrato.
Classes abstratas
//Classe Forma
abstract class Forma {
protected float x,y; //visivel hierarquia abaixo
public void move(float dx,float dy)
{
this.x+=dx; this.y+=dy;
}
abstract public void mostra(); //metodo abstrato
}
//Classe ponto
class Ponto extends Forma {
public Ponto(float ax,float ay) //omita o valor de retorno!
//garante o estado do objeto
{
this.x=ax; this.y=ay;
}
//move nao precisa ser redefinido
public void mostra()
{
System.out.println("("+this.x+","+this.y+")");
}
}
//Classe Retangulo
class Retangulo extends Forma {
protected float dx,dy; //delta x e delta y
//protected acaba sendo menos inflexivel e mais eficiente que
private
public Retangulo(float ax,float ay,float dx,float dy)
//garante o estado do objeto
{
x=ax; y=ay;
this.dx=dx; this.dy=dy; //this usado para eliminar ambiguidade
}
//metodo move nao precisa ser redefinido
public void mostra()
{
System.out.println("("+this.x+","+this.y+")("+dx+","+dy+")");
}
}
//Classe principal, Arquivo Principal.java
class Principal {
public static void main(String args[]) {
Retangulo ar=new Retangulo(0,0,13,14);
ar.mostra();
ar.move(1,1);
ar.mostra();
}
}
(0,0)(13,14) (1,1)(13,14)
//COMENTARIOS:
Observe que a classe Forma que é abstrata, não possui
um construtor, porque não pode ser instanciada. Agora também
temos um novo qualificador de classe e de métodos: abstract.
Classes abstratas X Interfaces:
Você deve estar achando que classes abstratas e interfaces
são conceitos parecidos e que podem ser usados com objetivos
semelhantes. Cuidado! Uma classe pode estender uma única
classe (que pode ser abstrata ou não), mas pode implementar
várias interfaces. Além disso, interfaces não
permitem declaração de atributos, enquanto que classes
abstratas permitem. Interfaces estão mais ligadas a comportamento,
enquanto que classes abstratas estão mais ligadas a implementação.
Exercícios:
1-
Defina uma classe abstrata tipo numérico que deve servir
como classe base para a montagem de classes como a classe fração
ou a classe número complexo. Uma boa medida da qualidade
de sua implementação é a quantidade de mudanças
necessárias para por exemplo trocar a classe fração
usada em um algoritmo de cálculo numérico pela classe
número complexo. É bem verdade que existem operações
que se aplicam a uma dessas classes, mas não a outra, mas
essas disparidades deverão ser mantidas fora da classe
base abstrata.
Neste exemplo iremos criar uma classe base abstrata iterador, que servirá como topo de uma hierarquia para iteradores de estruturas de dados como listas, vetores e árvores. O iterador de vetor é definido por herança da classe base abstrata de iteradores.
Perceba que alguns métodos da classe base são desprovidos de implementação, porém nada impede que você coloque como código desses métodos uma mensagem de erro do tipo "Erro, método deveria ter sido redefinido", mas agora o compilador não pode mais te lembrar de redefini-los.
"SHOPPING LIST APPROACH" PARA A CLASSE ABSTRATA ITERADOR VETOR
(Uma tarefa desagradável quando iterando sobre vetores é manter os índices das iterações atualizados durante avanços e retrocessos. Note que retrocessos não fariam sentido em uma lista simplesmente ligada, por isso essa operação somente é definida neste nível da hierarquia.)