Continuando com o exemplo da secção anterior, seria útil, por exemplo, construirmos métodos que imprimissem no standard output:
Por exemplo, algo como:
xstatic void imprimeDetalhes(???? j) {
System.out.println("========================================");
System.out.println("Detalhes do jogo:");
System.out.println(j.toString());
System.out.println("Melhor pontuacao ateh agora: " +
j.maiorPontuacao());
}
static void imprimeVencedores(???? j) {
System.out.println("========================================");
String tipoJogo = j.getClass().getName();
System.out.println("Vencedor(es) do jogo de tipo " +
tipoJogo + ":");
String[] venceram = j.vencedores();
for (String jog : venceram) {
System.out.println(jog + " com " + j.pontuacao(jog));
}
System.out.println("Parabens!");
}
Na classe ExemploPolimorfismoParametros
,
main
e main
acima listado que fazem a impressão das informações dos jogos no ecrã, seriam reduzidas a:xxxxxxxxxx
imprimeDetalhes(jogo1);
imprimeDetalhes(jogo2);
imprimeVencedores(jogo1);
imprimeVencedores(jogo2);
Pergunta:
Na definição de cada método, de que tipo deve ser o parâmetro que representa o jogo sobre o qual queremos atuar?
JogoComObjetivo
, então não poderíamos invocar o método dando como valor uma referência a um objeto do tipo JogoNumeroExatoJogadas
;JogoNumeroExatoJogadas
, então não poderíamos invocar o método dando como valor uma referência a um objeto do tipo JogoComObjetivo
.Resposta:
O tipo do parâmetro deve ser o tipo comum a qualquer jogo, ou seja, o tipo Jogo
.
xxxxxxxxxx
static void imprimeDetalhes(Jogo j) {
....
}
static void imprimeVencedores(Jogo j) {
....
}
Porquê? Relembrando que:
Na análise estática de tipos, executada pelo compilador, as seguintes regras são aplicadas:
- Seja
x
uma variável de um tipo não primitivoT
; numa atribuiçãox = expressao;
o tipo deexpressao
deve ser o mesmo que o dex
ou um subtipo deste;- Seja
x
, de tipo não primitivoT
, um parâmetro de um métodom
; numa invocaçãom(expressao);
, o tipo deexpressao
deve ser o mesmo que o dex
ou um subtipo deste.
percebemos então que as instruções
xxxxxxxxxx
imprimeDetalhes(jogo1);
imprimeDetalhes(jogo2);
imprimeVencedores(jogo1);
imprimeVencedores(jogo2);
estão corretas, pois tanto o tipo de jogo1
– JogoComObjetivo
–, como o tipo de jogo2
– JogoNumeroExatoJogadas
– são subtipos de Jogo
.
Chama-se polimorfismo à capacidade de tomar várias formas.
Nas invocações
imprimeDetalhes(jogo1);
imprimeDetalhes(jogo2);
imprimeVencedores(jogo1);
imprimeVencedores(jogo2);
dizemos que as instanciações de parâmetros são polimórficas pois o parâmetro dos métodos, que é do tipo
Jogo
, recebe valores de tipos que não são exatamente o tipoJogo
, embora sejam seus subtipos.
A razão por trás das regras aplicadas pelo compilador é intuitiva.
Suponha uma variável ou parâmetro x
declarada com o tipo T
:
o compilador só permite que invoquemos sobre x
os métodos acessíveis em T
;
se x
contém uma referência a um objeto que é exatamente do tipo T
então
se x
contém uma referência a um objeto de um subtipo de T
diferente de T
então
T
é herdado por esse subtipo,
No nosso exemplo, no corpo dos métodos imprimeDetalhes
e imprimeVencedores
os seguintes métodos são invocados sobre o parâmetro j
:
toString
: método implementado na classe Object
, redefinido na classe Jogo
e herdado por todas as subclasses desta;getClass
: método implementado na classe Object
, e portanto herdado pela classe Jogo
e por todas as subclasses desta;maiorPontuacao
, vencedores
e pontuacao
: métodos implementados na classe Jogo
e herdados por todas as subclasses desta.Ao parâmetro j
, declarado como sendo do tipo Jogo
, poderão ser atribuídas referências a objetos do tipo Jogo
ou de qualquer dos seus subtipos (para já temos dois, mas no futuro poderão ser mais). Qualquer que seja esse subtipo, sabemos que implementará todos os métodos invocados sobre j
, pois terão sido herdados (e possivelmente, até, redefinidos como, por exemplo, o método vencedores
, que vamos examinar a seguir com mais pormenor).
Então, a classe ExemploPolimorfismoParametros
ficaria assim:
xxxxxxxxxx
public class ExemploPolimorfismoParametros {
public static void main(String[] args) {
JogoComObjetivo jogo1 = new JogoComObjetivo("Argon", 25);
JogoNumeroExatoJogadas jogo2 =
new JogoNumeroExatoJogadas("Harvest Kingdom",2);
jogo1.juntarJogador("Maria");
jogo1.juntarJogador("Pedro");
jogo1.registarPontosJogada("Maria", 11);
jogo1.registarPontosJogada("Pedro", 26);
jogo2.juntarJogador("John Snow");
jogo2.juntarJogador("Daenerys Targaryen");
jogo2.registarPontosJogada("John Snow", 10);
jogo2.registarPontosJogada("Daenerys Targaryen", 10);
imprimeDetalhes(jogo1);
imprimeDetalhes(jogo2);
imprimeVencedores(jogo1);
imprimeVencedores(jogo2);
}
/**
* Imprime detalhes de um dado jogo
* @param j O jogo
* @requires j != null
*/
static void imprimeDetalhes(Jogo j) {
System.out.println("========================================");
System.out.println("Detalhes do jogo:");
System.out.println(j.toString());
System.out.println("Melhor pontuacao ateh agora: " +
j.maiorPontuacao());
}
/**
* Imprime vencedores de um dado jogo e respetivas pontuacoes
* @param j O jogo
* @requires j != null
*/
static void imprimeVencedores(Jogo j) {
System.out.println("========================================");
String tipoJogo = j.getClass().getName();
System.out.println("Vencedor(es) do jogo de tipo " +
tipoJogo + ":");
List<String> venceram = j.vencedores();
for (String jog : venceram) {
System.out.println(jog + " com " + jogo2.pontuacao(jog));
}
System.out.println("Parabens!");
}
}
Anterior: 15. Polimorfismo e ligação dinâmica
Seguinte: 15.2. Verificação estática de tipos