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