Programação por Objectos Java

Propaganda
Programação por Objectos
Java
Parte 8: Tipos genéricos
LEEC@IST
Java – 1/68
Tipos genéricos – revisão
• Um tipo genérico é um tipo que recebe
como argumento outros tipos (não
primitivos). Os tipos genéricos são ainda
conhecidos como tipos parametrizados.
• Os tipos genéricos são muito utilizadas na
definição de colecções (conjuntos, listas,
pilhas, árvores, etc).
LEEC@IST
Java – 2/68
Tipos genéricos (1)
Conjunto
T
+ adicionar(elem: T)
+ remover(elem: T)
public class Conjunto<T> {
public void adicionar(T elem) {...}
public void remover(T elem) {...}
}
LEEC@IST
Java – 3/68
Tipos genéricos (2)
public class Elemento<T> {
protected T elemento;
protected Elemento<T> próximoElemento;
public Elemento(T elemento) {
this.elemento = elemento;
}
public Elemento(T elemento, Elemento<T> próximoElemento) {
this.elemento = elemento;
this.próximoElemento = próximoElemento;
}
}
• Para criar um objecto do tipo Elemento é necessário dizer ao compilador
que tipo substituir por T. Por exemplo, um elemento que contém uma
String é construído da seguinte forma:
Elemento<String> strElem = new Elemento<String>("Hello");
LEEC@IST
Java – 4/68
Tipos genéricos (3)
public class Conjunto<T> {
protected int numElementos;
protected Elemento<T> primeiroElemento;
public void adicionar(T elem) {
//verificar se elemento já existe no conjunto
for (Elemento<T> aux=primeiroElemento;
aux!=null;
aux=aux.próximoElemento)
if (elem.equals(aux.elemento)) return;
//se não existir adicionar no início do conjunto
primeiroElemento =
new Elemento<T>(elem,primeiroElemento);
numElementos++;
}
public void remover(T elem) {...}
}
LEEC@IST
Java – 5/68
Tipos genéricos (4)
• Mais uma vez, para criar um objecto do tipo Conjunto
é necessário dizer ao compilador que tipo substituir
por T. Por exemplo, um conjunto de String’s é
construído da seguinte forma:
Conjunto<String> cj = new Conjunto<String>();
• Adicionar String’s ao conjunto seria:
cj.adicionar(“Hello”);
cj.adicionar(“World”);
cj.adicionar(“!”);
cj.adicionar(34); //Inválido
LEEC@IST
Java – 6/68
Tipos genéricos (5)
• Declarar a variável cj de tipo Conjunto<String>
informa o compilador que cj é uma referência para
um objecto do tipo Conjunto<T> onde T é String.
– Não é criada uma classe Conjunto<String>.
– Os tipos Conjunto<String> e Conjunto<Number> não são
duas classes distintas.
• Tal como para os métodos, nos tipos genéricos
também existe o conceito de parâmetro/argumento:
– No contexto de Conjunto<T>, T é dito parâmetro (tipo).
– No contexto de Conjunto<String>, String é dito
argumento (tipo).
LEEC@IST
Java – 7/68
Tipos genéricos (6)
public class Conjunto<T> {
protected int numElementos;
protected Elemento<T> primeiroElemento;
private static int numProxConjunto=0;
private int numConjunto;
public Conjunto() {
numConjunto = numProxConjunto++;
}
public int numConjunto() {
return numConjunto;
}
public static int numProxConjunto() {
return numProxConjunto;
}
public void adicionar(T elem) {...}
public void remover(T elem) {...}
}
LEEC@IST
Java – 8/68
Tipos genéricos (7)
Conjunto<String> cj_s = new Conjunto<String>();
System.out.println(“cj_s é o conjunto número ”
+ cj_s.numConjunto());
Conjunto<Integer> cj_i = new Conjunto<Integer>();
System.out.println(“cj_i é o conjunto número ”
+ cj_i.numConjunto());
No terminal é impresso cj_s é o conjunto número 0
cj_i é o conjunto número 1
LEEC@IST
Java – 9/68
Tipos genéricos (8)
• Consequências:
– O parâmetro tipo T não pode ser usado num contexto
estático.
– O acesso a membros estáticos das classes parametrizadas
não podem ser feitos através do nome da classe
parametrizada:
Conjunto<String>.numProxConjunto(); //INVÁLIDO!!!
Conjunto.numProxConjunto();
LEEC@IST
Java – 10/68
Tipos genéricos (9)
• Não é possível usar o parâmetro tipo T para criar
objectos nem para criar tabelas.
public class Conjunto<T> {
// ...
public T[] converterConjuntoParaTabela() {
T[] res = new T[numElementos]; //INVÁLIDO!!!
//... copiar elementos do conjunto para a tabela
}
}
LEEC@IST
Java – 11/68
Tipos genéricos (10)
• Solução 1: passar a tabela por parâmetro e o método
converterConjuntoParaTabela preenche a tabela
com os elementos do respectivo conjunto.
public class Conjunto<T> {
// ...
public T[] converterConjuntoParaTabela(T[] tabela) {
int i = 0;
for (Elemento<T> aux=primeiroElemento;
aux!=null;
aux=aux.próximoElemento)
tabela[i++] = aux.elemento;
return tabela;
}
}
LEEC@IST
Java – 12/68
Tipos genéricos (11)
• Na definição de tipos genéricos é possível restringir
o tipo do argumento passado ao parâmetro tipo T:
interface ColecçãoOrdenada<T extends Comparable<T>> {...}
– O tipo do argumento passado ao parâmetro tipo T
implementa os métodos da interface Comparable<T>.
interface ColecçãoOrdenadaSequênciaCaracteres<
T extends Comparable<T> & CharSequence> {...}
– O tipo do argumento passado ao parâmetro tipo T
implementa os métodos da interface Comparable<T> e
implementa os métodos da interface CharSequence.
LEEC@IST
Java – 13/68
Tipos genéricos (12)
• A palavra chave extends é usada nos parâmetros tipo
dos tipos genéricos de uma forma muito geral:
– Significando extends se o tipo que se segue é uma classe.
– Significando implements se o tipo que se segue é uma
interface.
– Um tipo genérico apenad pode estender uma classe e um
conjunto de interfaces, ou seja só podemos ter declarações da
forma:
• Conjunto<T estends [class | interface] & interfacelist>
LEEC@IST
Java – 14/68
Tipos genéricos aninhados (1)
• Um tipo aninhado pode ser um tipo genérico.
• Se um tipo genérico for aninhado noutro tipo
genérico:
– Se o tipo genérico aninhado for estático, o parâmetro tipo do
tipo aninhado é independente do parâmetro tipo do tipo
genérico que o engloba, mesmo que tenham o mesmo
nome.
– Se o tipo genérico aninhado não for estático, então o
parâmetro tipo do tipo genérico que o engloba é acessível
no tipo aninhado, portanto pode ser usado directamente
(sempre que tal fizer sentido).
LEEC@IST
Java – 15/68
Tipos genéricos aninhados (2)
public class Conjunto<T> {
static class Elemento<E> {
protected E elemento;
protected Elemento<E> próximoElemento;
public Elemento(E elemento) {
this.elemento = elemento;
}
public Elemento(E elemento, Elemento<E> próximoElemento) {
this.elemento = elemento;
this.próximoElemento = próximoElemento;
}
}
protected int numElementos;
protected Elemento<T> primeiroElemento;
// ...
}
LEEC@IST
Java – 16/68
Tipos genéricos aninhados (3)
• Poderíamos ter parâmetros tipo com o mesmo
nome, por exemplo, T, sendo na mesma ambos
distintos:
public class Conjunto<T> {
static class Elemento<T> {
protected T elemento;
protected Elemento<T> próximoElemento;
// ...
}
protected Elemento<T> primeiroElemento;
// ...
}
LEEC@IST
Java – 17/68
Tipos genéricos aninhados (4)
public class Conjunto<T> {
class Elemento {
protected T elemento;
protected Elemento<T> próximoElemento;
public Elemento(T elemento) {
this.elemento = elemento;
}
public Elemento(T elemento, Elemento<T> próximoElemento) {
this.elemento = elemento;
this.próximoElemento = próximoElemento;
}
}
protected int numElementos;
protected Elemento<T> primeiroElemento;
// ...
}
LEEC@IST
Java – 18/68
Utilização de tipos genéricos (1)
• Os tipos genéricos podem ser utilizados no contexto
de declaração/instanciação de tipos:
–
–
–
–
tipo de atributos;
tipo de variáveis locais;
tipo dos parâmetros de métodos;
tipo de retorno de métodos.
LEEC@IST
Java – 19/68
Utilização de tipos genéricos (2)
• Considere um método que soma os elementos em
Conjunto<Number>:
static double soma(Conjunto<Number> cj) {
double res = 0.0;
for (Elemento<Number> aux=cj.primeiroElemento;
aux!=null;
aux=aux.próximoElemento)
res+=aux.elemento.doubleValue();
return res;
}
LEEC@IST
Java – 20/68
Utilização de tipos genéricos (3)
• Se tentarmos chamar o método soma com um
Conjunto<Integer>, o código não compila:
Conjunto<Integer> cj = new Conjunto<Integer>();
cj.adicionar(1);
cj.adicionar(2);
double soma = soma(cj); //INVÁLIDO!!!
• Apesar de Integer ser um subtipo de Number,
Conjunto<Integer> não é um subtipo de
Conjunto<Number>.
– Por exemplo Conjunto<Integer> não contém o método
cj.adicionar(Number x) de Conjunto<Number>
LEEC@IST
Java – 21/68
Utilização de tipos genéricos (4)
• A solução é definir o parâmetro do método soma
como um conjunto de Number ou de um qualquer
subtipo deste:
static double soma(Conjunto<? extends Number> cj) {
double res = 0.0;
for (Elemento<? extends Number> aux=cj.primeiroElemento;
aux!=null;
aux=aux.próximoElemento)
res+=aux.elemento.doubleValue();
return res;
}
• O ? extends no parâmetro tipo refere-se a um
Number ou a uma subclasse de Number.
• ? é referido como um wildcard
LEEC@IST
Java – 22/68
Utilização de tipos genéricos (5)
• Também é possível definir o parâmetro tipo de tal
forma que seja de um determinado tipo ou de um
qualquer supertipo deste.
– Conjunto<? super Integer> denota um conjunto de
Integer, ou de um supertipo de Integer:
•
•
•
•
Conjunto<Integer>
Conjunto<Number>
Conjunto<Object>
…
• O extends e o super não podem ser usados em
simultâneo.
• Extends implementa um “uper bound” wildcard
enquanto que super implementa um “lower bound”
wildcard.
LEEC@IST
Java – 23/68
Utilização de tipos genéricos
•
Utilização do super
– Suponha que pretende implementar uma interface de uma lista
ordenada genérica, para tal necessita de um tipo para o qual esteja
definida uma ordem, ou seja implemente o interface
Comparable<E>
Ex: interface SortedCollection<E extends
Comparable<E>>
No entanto é suficiente que E implemente Comparable para
qualquer supertipo de E, por exemplo basta que exista E.
compareTo(Object), pelo que mais correctamente devemos
fazer:
Ex: interface SortedCollection<E extends
Comparable<? Super E>>
LEEC@IST
Java – 24/68
Utilização de tipos genéricos (6)
• Também é possível definir o parâmetro tipo de tal
forma que seja de um qualquer tipo.
– O Conjunto<?> denota um conjunto de qualquer tipo.
– Implicitamente, Conjunto<?> refere-se a um qualquer
tipo desde que seja um Object ou uma subclasse deste.
•
LEEC@IST
O Conjunto<?> é outra forma de dizer Conjunto<?
extends Object> (e não Conjunto<Object>).
Java – 25/68
Utilização de tipos genéricos (7)
Conjunto<?>
Object
Number
Conjunto<Object>
Conjunto<? extends Number>
Integer
Conjunto<Number>
Conjunto<? extends Integer>
Conjunto<Integer>
LEEC@IST
Java – 26/68
Utilização de tipos genéricos (8)
•
Como o ? representa um tipo desconhecido, não é possível
chamar nenhum método da classe parametrizada que receba o
parâmetro tipo como parâmetro:
Conjunto<?> cj = new Conjunto<Integer>();
cj.adicionar(new Integer(2)); //INVÁLIDO!!!
Conjunto<? extends Integer> cj = new Conjunto<Integer>();
cj.adicionar(new Integer(2)); //INVÁLIDO!!!
Conjunto<? super Integer> cj = new Conjunto<Integer>();
cj.adicionar(new Integer(2));
Conjunto<? super Integer> cj = new Conjunto<Integer>();
cj.adicionar(1.0); // INVÁLIDO!!!
cj.adicionar(new Object()); //INVÁLIDO!!!
LEEC@IST
Java – 27/68
Utilização de tipos genéricos
void method(Conjunto<? extends Number> cj);
cj pode ser instanciada como
new Conjunto<Number>()
Aceita: cj.adicionar(new Integer(2));
Aceita: cj.adicionar(new Long(2));
Aceita: cj.adicionar(new Float(2));
new Conjunto<Integer>()
Aceita: cj.adicionar(new Integer(2));
new Conjunto<Long>()
Aceita: cj.adicionar(new Long(2));
new Conjunto<Float>()
Aceita: cj.adicionar(new Float(2));
não há métodos comuns a todas as instancias pelo que a chamada a
cj.adicionar(T x) é inválida.
No entanto posso chamar qualquer método de Number.
LEEC@IST
Java – 28/68
Utilização de tipos genéricos
void method(Conjunto<? super Integer> cj);
cj pode ser instanciada como
new Conjunto<Integer>()
Aceita: cj.adicionar(new Integer(2));
new Conjunto<Number>()
Aceita: cj.adicionar(new Integer(2));
Aceita: cj.adicionar(new Long(2));
Aceita: cj.adicionar(new Float(2));
new Conjunto<Object>()
Aceita: cj.adicionar(new Integer(2));
Aceita: cj.adicionar(new Long(2));
Aceita: cj.adicionar(new Float(2));
Aceita: cj.adicionar(new String(2));
O único método comum é:
cj.adicionar(new Integer(2));
LEEC@IST
Java – 29/68
Utilização de tipos genéricos (9)
Uma instancia de
Conjunto<Integer>
Pode ser atribuída a diferente declarações de variáveis:
Conjunto<?> cj = new Conjunto<Integer>();
System.out.println(cj.numConjunto());
Conjunto<? extends Integer> cj = new Conjunto<Integer>();
System.out.println(cj.numConjunto());
Conjunto<? super Integer> cj = new Conjunto<Integer>();
System.out.println(cj.numConjunto());
LEEC@IST
Java – 30/68
Utilização de tipos genéricos (10)
• O ? só pode ser utilizados no contexto de
declaração de tipos de variáveis:
–
–
–
–
tipo de atributos;
tipo de variáveis locais;
tipo dos parâmetros de métodos;
tipo de retorno de métodos.
LEEC@IST
Java – 31/68
Métodos e construtores
genéricos (1)
public class Conjunto<T> {
// ...
public T[] converterConjuntoParaTabela(T[] tabela) {
//... copiar elementos do conjunto para a tabela
return tabela;
}
}
• O método converterConjuntoParaTabela tal como
está definido é demasiado restritivo (ver slide 11 e 12):
– Num objecto de tipo Conjunto<Integer> temos de passar
como argumento uma tabela Integer[].
– Num objecto de tipo Conjunto<Integer> nunca poderiamos
passar uma tabela Object[], mesmo sendo válido guardar
objectos do tipo Integer numa tal tabela.
LEEC@IST
Java – 32/68
Métodos e construtores
genéricos (2)
• Um método genérico é declarado definindo o
parâmetro tipo entre os qualificadores e o tipo de
retorno do método:
public class Conjunto<T> {
//...
public <E> E[] converterConjuntoParaTabela(E[] tabela) {
Object[] tmp = tabela;
int i = 0;
for (Elemento<T> aux=primeiroElemento;
aux!=null;
aux=aux.próximoElemento)
tmp[i++] = aux.elemento;
return tabela;
}
}
LEEC@IST
Java – 33/68
Métodos e construtores
genéricos (3)
Conjunto<Integer> cj = new Conjunto<Integer>();
Object[] tocj = new Object[cj.numElementos];
tocj = cj.converterConjuntoParaTabela(tocj);
Conjunto<Integer> cj = new Conjunto<Integer>();
Integer[] ticj = new Integer[cj.numElementos];
ticj = cj.converterConjuntoParaTabela(ticj);
LEEC@IST
Java – 34/68
Métodos e construtores
genéricos (4)
• Um método ou construtor genérico não precisa de
ser declarado dentro duma classe genérica, mas se
o for os parâmetros tipo são distintos.
LEEC@IST
Java – 35/68
Métodos e construtores
genéricos (5)
• O método converterConjuntoParaTabela pode ser
chamado da seguinte forma:
Conjunto<Integer> cj = new Conjunto<Integer>();
Integer[] tcj = new Integer[cj.numElementos];
tcj = cj.<Integer>converterConjuntoParaTabela(tcj);
• Esta chamada parametrizada informa o compilador que
o parâmetro tipo E do método
converterConjuntoParaTabela deve ser tratado
com um Integer, e que os argumentos e o retorno
devem estar de acordo com tal.
LEEC@IST
Java – 36/68
Métodos e construtores
genéricos (6)
• A chamada parametrizada só muito raramente é
necessária. O compilador consegue, de uma maneira
geral, fazer a inferência do tipo em causa:
Conjunto<Integer> cj = new Conjunto<Integer>();
Integer[] tcj = new Integer[cj.numElementos];
tcj = cj.converterConjuntoParaTabela(tcj);
• A inferência do tipo é baseada no tipo estático do
argumento passado ao método ou construtor
genérico (não nos seus tipos dinâmicos).
– Tipo declarado ou cast explícito.
LEEC@IST
Java – 37/68
Métodos e construtores
genéricos (7)
<T> T passarObjecto(T obj) {
return obj;
}
String s1 = “Hello”;
String s2 = this.<String>passarObjecto(s1);
String
String
Object
Object
s1
s2
o1
o2
=
=
=
=
“Hello”;
passarObjecto(s1);
passarObjecto(s1);
passarObjecto((Object)s1);
// T -> String
// T -> String
// T -> Object
String s1 = “Hello”;
s1 = passarObjecto((Object)s1); //INVÁLIDO(só com cast)!!!
String s1 = “Hello”;
s1 = (String) passarObjecto((Object)s1);
LEEC@IST
// T -> Object
Java – 38/68
Métodos e construtores
genéricos (8)
• A chamada parametrizada tem de ser sempre feita
sempre através do nome qualificado:
–
–
–
–
this.<Tipo>método(params);
super.<Tipo>método(params);
ref.<Tipo>método(params);
IdentC.<Tipo>métodos(params);
para métodos estáticos, onde IdentC é o identificador da
respectiva classe.
String s1 = “Hello”;
String s2 = <String>passarObjecto(s1); //INVÁLIDO!!!
LEEC@IST
Java – 39/68
Subtipos e tipos genéricos (1)
• É possível:
– Definir um subtipo (genérico ou não) a partir de um
supertipo não genérico.
– Definir uma subtipo (genérico ou não) a partir de um
supertipo genérico.
class
class
class
class
...
ListaGeral<E> implements List<E> {...}
ListaStrings implements List<Strings> {...}
ConjuntoNúmeros<T extends Number> extends Conjunto<T> {...}
ConjuntoInteiros extends Conjunto<Integer> {...}
LEEC@IST
Java – 40/68
Subtipos e tipos genéricos (2)
• Uma classe não pode implementar duas interfaces
que sejam diferentes parametrizações da mesma
interface.
class Valor implements Comparable<Valor> {...}
class ValorEstendido extends Valor
implements Comparable<ValorEstendido> {...} //INVÁLIDO!!!
• Nesse caso, a classe ValorEstendido teria de
implementar ambas as interfaces
Comparable<Valor> e
Comparable<ValorEstendido> (o que não é
permitido).
LEEC@IST
Java – 41/68
Apagamento (1)
• Para cada tipo genérico há apenas um tipo.
– Que tipo é esse?
– Dada a classe Conjunto<T>, que tipo
Conjunto<Integer> e Conjunto<Number> partilham?
• Na realidade o compilador apaga toda a informação
do parâmetro tipo na classe compilada:
– O Conjunto<T> é na classe compilada apenas
Conjunto.
– O parâmetro tipo T é na classe compilada:
• Object, quando se encontra num contexto <T> (Object é
a superclasse implícita de T).
• Number, quando se encontra num contexto <T extends
Number> (Number é a superclasse explícita de T).
LEEC@IST
Java – 42/68
Apagamento (2)
• A informação que é apagada do tipo parametrizado é
denominada o apagamento (erasure).
– Conjunto é o apagamento de Conjunto<T>.
– Object é o apagamento de T num contexto <T>.
– Number é o apagamento de T num contexto <T extends
Number>.
• O apagamento de um tipo genérico é também
denominado o tipo crú (raw type).
– Conjunto é o tipo crú de Conjunto<T>.
• O compilador gera a definição de um tipo para o
apagamento do tipo genérico substituindo todas as
utilizações do parâmetro tipo pelo correspondente
apagamento.
LEEC@IST
Java – 43/68
Apagamento (3)
public class PassarObjecto<T> {
T passarObjecto(T t) { return t; }
}
public class PassarObjecto {
Object passarObjecto(Object t) { return t; }
}
•
Quando o tipo parametrizado é usado e a informação sobre o tipo
resultante do apagamento é insuficiente, o compilador insere um
cast.
PassarObjecto<String> pos = new PassarObjecto<String>();
String s1 = “Hello”;
s1 = pos.passarObjecto(s1);
PassarObjecto pos = new PassarObjecto();
String s1 = “Hello”;
s1 = (String) pos.passarObjecto(s1);
LEEC@IST
Java – 44/68
Apagamento (4)
public class Elemento<T> {
protected T elemento;
protected Elemento<T> próximoElemento;
public Elemento(T elemento) {...}
public Elemento(T elemento, Elemento<T> próximoElemento) {...}
}
public class Elemento {
protected Object elemento;
protected Elemento próximoElemento;
public Elemento(Object elemento) {...}
public Elemento(Object elemento, Elemento próximoElemento) {...}
}
LEEC@IST
Java – 45/68
Apagamento (5)
public class Conjunto<T> {
protected int numElementos;
protected Elemento<T> primeiroElemento;
public void adicionar(T elem) {...}
public void remover(T elem) {...}
public T escolher() {
return primeiroElemento.elemento;
}
public class Conjunto {
}
protected int numElementos;
protected Elemento primeiroElemento;
public void adicionar(Object elem) {...}
public void remover(Object elem) {...}
public Object escolher() {
return primeiroElemento.elemento;
}
}
LEEC@IST
Java – 46/68
Apagamento (6)
Conjunto<String> cjs = new Conjunto<String>();
String s1 = “Hello”;
cjs.adicionar(s1);
s1 = cjs.escolher();
Conjunto cjs = new Conjunto();
String s1 = “Hello”;
cjs.adicionar(s1);
s1 = (String) cjs.escolher();
LEEC@IST
Java – 47/68
Apagamento (7)
• O apagamento faz com que, em tempo de execução,
não seja permitido nada que necessite do
conhecimento do argumento tipo:
1. Não é possível usar o parâmetro tipo T para
criar objectos nem para criar tabelas.
public class Conjunto<T> {
// ...
public T[] converterConjuntoParaTabela() {
T[] res = new T[numElementos]; //INVÁLIDO!!!
//... copiar elementos do conjunto para a tabela
}
}
LEEC@IST
Java – 48/68
Apagamento (8)
2. Não é possível criar tabelas cujos elementos
sejam tipos parametrizados, com a excepção de
tipos parametrizados com ?.
Conjunto<String>[] tcjs = new Conjunto<String>[2]; //INVÁLIDO!!!
Conjunto<?>[] tabela = new Conjunto<?>[2];
tabela[0] = new Conjunto<String>();
tabela[1] = new Conjunto<Integer>();
((Conjunto<String>)tabela[0]).adicionar("Hello");
((Conjunto<Integer>)tabela[1]).adicionar(1);
System.out.println(tabela[0].escolher());
System.out.println(tabela[1].escolher());
LEEC@IST
Java – 49/68
Apagamento (9)
3. Não é possível usar o instanceof para verificar
se um objecto é uma instância dum tipo
parametrizado, com a exepção de tipos
parametrizados com ?.
Conjunto<String> strings = new Conjunto<String>();
boolean b = strings instanceof Conjunto<?>;
boolean b = strings instanceof Conjunto<String>; //INVÁLIDO!!!
Conjunto<?> strings = new Conjunto<String>();
System.out.println(strings instanceof Conjunto<?>);
System.out.println(strings instanceof Conjunto<String>); //INVÁLIDO!!!
LEEC@IST
Java – 50/68
Apagamento (10)
4. Os casts envolvendo tipos parametrizados ou
parâmetros tipo são substituídos por casts para
os correspondentes apagamentos.
– Usualmente, tal faz com que o compilador gere
unchecked warnings: o cast não pode ser verificado
em tempo de execução, nem tem garantia de ser
seguro em tempo de compilação.
LEEC@IST
Java – 51/68
Apagamento (11)
void adicionarStringHello(Conjunto<?> cj) {
Conjunto<String> strings = (Conjunto<String>) cj; //unchecked
strings.adicionar(“Hello”);
}
void adicionarStringHello(Conjunto cj) {
O erro em tempo de
Conjunto strings = (Conjunto) cj;
strings.adicionar(“Hello”);
execução surgirá
}
numa linha diferente
de onde está o erro….
Conjunto<Integer> integers = new Conjunto<Integer>();
adicionarStringHello(integers);
//ok
Integer nb = integers.escolher(); //erro em tempo de execução!!!
Conjunto integers = new Conjunto();
adicionarStringHello(integers);
Integer nb = (Integer) integers.escolher();
LEEC@IST
Java – 52/68
Apagamento (12)
Object passarObjecto(Objecto obj) {
return obj;
}
Conjunto<String> strings = new Conjunto<String>();
Object obj = passarObjecto(strings);
Conjunto<String> cj1 = (Conjunto<String>) obj; // unckecked
Conjunto<String> cj2 = (Conjunto) obj;
// unckecked e tipo crú
Conjunto<?> cj3 = (Conjunto) obj;
// ok mas tipo crú
Conjunto<?> cj4 = (Conjunto<?>) obj;
// ok
•
Os tipos crús existem por razões de código legado,
por isso devem ser evitados.
LEEC@IST
Java – 53/68
Limites de parâmetros e
argumentos tipo (1)
• Parâmetro (tipo)
(na definição de métodos ou tipos parametrizados)
void foo(int n, char c) {...} //n e c são parâmetros
class Conjunto<T> {...}
//T é um parâmetro tipo
<T> T escolher() {...}
//T é um parâmetro tipo
• Argumento (tipo)
(na chamada de métodos ou declaração/instanciação
de tipos parametrizados)
foo(5,‘a’);
Conjunto<?> cj1;
cj1 = new Conjunto<String>;
this.<String>escolher();
LEEC@IST
//5 e ‘a’ são argumentos
//? é um argumento tipo
//String é um argumento tipo
//String é um argumento tipo
Java – 54/68
Limites de parâmetros e
argumentos tipo (2)
• Na definição de tipos/métodos genéricos, um
parâmetro tipo da forma:
– <T> diz-se um parâmetro tipo simples.
– <T extends Number> diz-se um parâmetro tipo com limite
superior (neste caso Number).
• Na declaração de tipos genéricos, um argumento tipo
da forma:
– <T> diz-se um argumento tipo simples.
– <T extends Number> ou <? extends Number> diz-se um
argumento tipo com limite superior (neste caso Number).
– <T super Number> ou <? super Number> diz-se um
argumento tipo com limite inferior (neste caso Number).
– <?> diz-se um argumento tipo ilimitado.
LEEC@IST
Java – 55/68
Sobreposição em
tipos genéricos (1)
•
•
•
•
No Java, a assinatura de um método é definida por:
– Identificador do método.
– Número e tipo dos parâmetros do método.
A assinatura de um método genérico é definida por:
– Identificador do método.
– Número e tipo de parâmetros (incluindo parâmetros e limites
tipo do método).
Dois métodos têm assinaturas equivalentes (overrideequivalente signature) se têm a mesma assinatura, ou se após o
apagamento têm a mesma assinatura.
Um método é uma sobreposição de outro se ambos têm o
mesmo identificador mas não têm assinaturas equivalentes.
LEEC@IST
Java – 56/68
Sobreposição em
tipos genéricos (2)
class SuperClasse<T> {
void m(int x) {}
void m(T t) {}
void m(String s) {}
<N extends Number> void m(N n) {}
void m(Conjunto<?> cj) {}
}
•
A classe SuperClasse define cinco sobreposições do método m:
–
–
–
–
–
void
void
void
void
void
LEEC@IST
m(int x) {}
m(Object t) {}
m(String s) {}
m(Number n) {}
m(Conjunto cj) {}
Java – 57/68
Sobreposição em
tipos genéricos (3)
•
•
É um erro uma classe ou interface declarar dois métodos com o
mesmo identificador e a mesma assinatura após apagamento.
Definir qualquer dos seguintes métodos na classe SuperClasse
daria erro:
–
–
–
–
void m(Object o) {}
void m(Number n) {}
<G extends String> void m(G g) {}
void m(Conjunto<Object> cj) {}
class SuperClasse<T> {
void m(int x) {}
void m(T t) {}
void m(String s) {}
<N extends Number> void m(N n) {}
void m(Conjunto<?> cj) {}
}
LEEC@IST
Java – 58/68
Redefinição em
tipos genéricos (1)
•
•
Um método num subtipo é uma redefinição dum método do
supertipo se:
1. Ambos os métodos têm assinaturas equivalentes.
2. O retorno dos métodos tem de ser covariante (antes do
apagamento)
Temos ainda que:
–
–
–
Um método genérico não pode redefinir um método não genérico.
Um método genérico pode redefinir um método genérico.
Um método não genérico pode redefinir um método genérico.
LEEC@IST
Java – 59/68
Redefinição em
tipos genéricos (2)
class SuperClasse<T> {
void m(int x) {}
void m(T t) {}
void m(String s) {}
<N extends Number> void m(N n) {}
void m(Conjunto<?> cj) {}
}
class SuperClasse {
void m(int x) {}
void m(Object t) {}
void m(String s) {}
void m(Number n) {}
void m(Conjunto cj) {}
}
class SubClasse<T> extends SuperClasse<T> {
void m(Integer i) {}
void m(Object t) {}
void m(Number s) {}
}
LEEC@IST
Java – 60/68
Redefinição em
tipos genéricos (3)
• Em relação ao exemplo anterior:
– O método m(Integer i) é uma sobreposição dos
métodos m da SubClasse.
– O método m(Object t) da SubClasse é uma redefinição
do método m(T t) da SuperClasse.
– O método m(Number cj) da SubClasse é uma
redefinição do método m(N n) da SuperClasse.
LEEC@IST
Java – 61/68
Redefinição em
tipos genéricos (4)
class SuperClasse<T> {
void m(int x) {}
void m(T t) {}
void m(String s) {}
<N extends Number> void m(N n) {}
void m(Conjunto<?> cj) {}
}
class SuperClasse {
void m(int x) {}
void m(Object t) {}
void m(String s) {}
void m(Number n) {}
void m(Conjunto cj) {}
}
class SubClasse<T> extends SuperClasse<T> {
void m(Number n) {}
//ok
<S extends String> void m(S s) {} //INVÁLIDO!!!
}
class SubClasse extends SuperClasse {
void m(Number n) {}
void m(String s) {}
}
LEEC@IST
Java – 62/68
Redefinição em
tipos genéricos (5)
• Em relação ao exemplo anterior:
– O método m(Number n) da SubClasse é uma redefinição
do método m(N n) da SuperClasse.
– O método m(S s) da SubClasse tenta fazer uma
redefinição do método m(String s) da SuperClasse,
mas tal não é permitido!
LEEC@IST
Java – 63/68
Redefinição em
tipos genéricos (6)
class SuperClasse {
protected Integer i;
<N extends Number> Number m1(N n) {return i;}
<N extends Number> N m2() {return (N) i;}
}
class SuperClasse {
protected Integer i;
Number m1(Number n) {return i;}
Number m2() {return (Number) i;}
}
class SubClasse extends SuperClasse {
<N extends Number> Integer m1(N n) {return i;}
<N extends Integer> N m2() {return (N) i;}
}
class SubClasse extends SuperClasse {
Integer m1(Number n) {return i;}
Integer m2() {return (Integer) i;}
}
LEEC@IST
Java – 64/68
Redefinição em
tipos genéricos (7)
– O método Integer m1(N n) da SubClasse é uma
redefinição do método Number m1(N n) da
SuperClasse.
– O método N m2() da SubClasse não é uma redefinição
do método N m2() da SuperClasse.
• <N extends Number> != <? extends Number>
• <N extends Number> N m2() pode implementar,
– Integer <Integer>m2()
– Long <Long>m2()
– Float <Float>m2() …
• <N extends Integer> N m2() pode implementar,
– Integer <Integer>m2()
• Pelo que na classe derivada não seriam redefinidas todas as
implementações do método pois Integer não é covariante com
Long ou Float
LEEC@IST
Java – 65/68
Redefinição em
tipos genéricos (8)
class SuperClasse {
protected Integer i;
<N extends Number> Number m1(N n) {return i;}
<N extends Number> N m2() {return (N) i;}
}
class SubClasse extends SuperClasse {
@Override //ok
<N extends Number> Integer m1(N n) {return i;}
Sobre-posição e chamada ambigua//!!!
<N extends Integer> N m2() {return (N) i;}
}
LEEC@IST
Java – 66/68
Herança e sobreposição de
métodos (revisitado) (1)
•
As mudanças ao algoritmo para escolha do método
mais específico para o caso de tipos genéricos são:
1. Se a chamada do método inclui argumentos tipo
explícitos:
–
São escolhidos como potencialmente aplicáveis os métodos
genéricos com o mesmo número de parâmetros tipo.
2. Se a chamada do método não inclui argumentos tipo
explícitos:
–
–
É feita a inferência do tipo (baseada no tipo estático dos
argumentos tipo passados na invocação do método) e
escolhem-se os métodos potencialmente aplicáveis.
Este passo é necessário para métodos em que por exemplo
o parâmetro tipo aparece de repetido. Estes não serão
considerados se os argumentos tipo forem diferentes. Ex:
–
LEEC@IST
<T> foo(T x, T y)
Java – 67/68
Herança e sobreposição de
métodos (revisitado) (2)
3. De todos os métodos aplicáveis é seleccionado
o mais especifico.
O método A é mais especifico do que o B se
qualquer chamada a A também pode ser feita a
B. A determinação do método mais especifico
será então efectuada levando em conta os
limites aos parâmetros tipo, e pode ser feita
determinando o método mais especifico depois
do apagamento.
LEEC@IST
Java – 68/68
Herança e sobreposição de
métodos (revisitado) (3)
void m(String s, Object o) {...}
// 1ª forma
<S, T extends Number> void m(S s, T t) {...} // 2ª forma
As seguintes chamadas:
• m(“hello”, “world”);
• m(new Object(), 29);
• m(“hello”, Integer.valueOf(29));
resultam na chamada de que forma do método m?
LEEC@IST
Java – 69/68
Herança e sobreposição de
métodos (revisitado) (4)
• Relativamente ao exemplo anterior:
– m(“hello”, “world”);
resulta na chamada da 1ª forma do método xpto.
– m(new Object(), 29);
resulta na chamada da 2ª forma do método xpto.
– m(“hello”, Integer.valueOf(29));
é inválida.
LEEC@IST
Java – 70/68
Herança e sobreposição de
métodos (revisitado) (5)
• No exemplo anterior,
m(“hello”, Integer.valueOf(29));
é uma chamada inválida, contudo:
– Com o cast (Object)”Hello” a chamada
m((Object)”Hello”, Integer.valueOf(29));
resulta na chamada da 2ª forma de m.
– Com o cast (Object) Integer.valueOf(29) a
chamada
m(“Hello”, (Object) Integer.valueOf(29));
resulta na chamada da 1ª forma de m.
LEEC@IST
Java – 71/68
Download