Um tipo diz-se genérico quando a sua definição envolve uma ou mais variáveis de tipo, que são instanciadas quando o tipo é usado na declaração de variáveis e na criação de objetos.
Por exemplo, ArrayList<E>
é um tipo genérico pois quando criamos um objeto deste tipo temos que instanciar a variável E
(variável de tipo).
Isto permite que se use uma só definição para a representação de vários tipos – tantos quantos os valores possíveis para as variáveis de tipo.
Esta noção de genérico também se aplica a métodos individualmente – podemos definir um método que deixa em aberto um ou mais tipos através da utilização de variáveis de tipo.
Vamos então perceber a utilidade deste conceito aplicado a métodos.
Queremos um método para ver se duas listas são iguais.
1ª tentativa:
xstatic boolean listasIguais(List<Object> a, List<Object> b) {
boolean result = a.size() == b.size();
for (int i = 0 ; i < a.size() && result ; i++) {
if(!a.get(i).equals(b.get(i))) {
result = false;
}
}
return result;
}
Este método não é muito útil pois, como já deve adivinhar, exige que se instanciem os parâmetros com listas de Object
. Das invocações no método main
que se segue, só a terceira está correta:
xxxxxxxxxx
public static void main( String[] args) {
ArrayList<String> l1 = new ArrayList<String>();
... // adicionar elementos a l1
List<String> l2 = new LinkedList<String>();
... // adicionar elementos a l2
ArrayList<Object> l3 = new ArrayList<Object>();
... // adicionar elementos a l3
ArrayList<Object> l4 = new ArrayList<Object>();
... // adicionar elementos a l4
boolean iguaisA = listasIguais(l1, l2);
boolean iguaisB = listasIguais(l1, l3);
boolean iguaisC = listasIguais(l3, l4);
}
Gostaríamos que o método aceitasse também listas de elementos de quaisquer tipos.
2ª tentativa:
xxxxxxxxxx
static boolean listasIguais(List<?> a, List<?> b) {
boolean result = a.size() == b.size();
for (int i = 0 ; i < a.size() && result ; i++) {
if(!a.get(i).equals(b.get(i))) {
result = false;
}
}
return result;
}
Esta versão já aceita listas de elementos de qualquer tipo. Até aceita duas listas cujos elementos são de tipos incomparáveis (por exemplo, Integer
e Jogo
).
Se quisermos garantir que os parâmetros a
e b
representam listas de elementos do mesmo tipo e que esse tipo pode ser qualquer, teremos que usar um parâmetro de tipo, tornando o método genérico.
Última tentativa:
xxxxxxxxxx
static <T> boolean listasIguais(List<T> a, List<T> b) {
boolean result = a.size() == b.size();
for (int i = 0 ; i < a.size() && result ; i++) {
if(!a.get(i).equals(b.get(i))) {
result = false;
}
}
return result;
}
Indicamos que o método é genérico colocando
< T >
antes do tipo de retorno do método.Por ser um parâmetro de tipo,
T
pode representar qualquer tipo; usamo-lo nos dois parâmetrosa
eb
para obrigar a que os elementos dessas listas tenham exatamente o mesmo tipo.Ao invocar um método genérico, não explicitamos qual o tipo que instancia
T
. O compilador faz isso por nós.
Um método genérico pode ter um ou mais parâmetros de tipo. Um exemplo com dois parâmetros de tipo:
xxxxxxxxxx
static <P,Q> Par<P,Q> emparelhaOuNull(P prim, Q seg) {
Par<P,Q> result = null;
if(prim != null && seg != null) {
result = new Par<P,Q>(prim, seg);
}
return result;
}
Exemplos de invocação:
xxxxxxxxxx
public static void main( String[] args) {
List<Object> lo1 = new ArrayList<Object>();
List<Object> lo2 = new ArrayList<Object>();
boolean b = listasIguais(lo1,lo2); // compilador infere que T e' Object
List<String> ls1 = new ArrayList<String>();
List<String> ls2 = new ArrayList<String>();
b = listasIguais(ls1,ls2); // compilador infere que T e' String
b = listasIguais(ls1,lo1); // compile-time error
List<Number> ln1 = new ArrayList<Number>();
List<Integer> li1 = new ArrayList<Integer>();
b = listasIguais(li1,ln1); // compile-time error
// compilador infere que P e' Integer e Q e' Double
Par<Integer, Double> par = emparelhaOuNull(23, 2.3);
}
Podemos restringir os parâmetros de tipo de um método genérico a um subconjunto dos tipos Java. Fazemos isso usando parâmetros de tipo bounded.
Método genérico para ver se os elementos de uma lista são o dobro dos de outra:
xxxxxxxxxx
static <T extends Number> boolean dobro(List<T> a, List<T> b) {
boolean result = a.size() == b.size();
for (int i = 0 ; i < a.size() && result ; i++){
double ad = a.get(i).doubleValue();
double bd = b.get(i).doubleValue();
if(!(Math.abs(ad - 2 * bd) < 0.0001)){
result = false;
}
}
return result;
}
Aqui o parâmetro de tipo é bounded pois queremos restringir o tipo dos elementos das listas parâmetro – têm que ser números.
Neste último método, as listas a
e b
têm que ter exatamente o mesmo tipo de elementos. Se quisermos que possam ser tipos diferentes, embora ambos subtipos de Number
, poderíamos fazer assim:
xxxxxxxxxx
static <U extends Number, V extends Number> boolean dobro(List<U> a, List<V> b) {
boolean result = a.size() == b.size();
for (int i = 0 ; i < a.size() && result ; i++){
double ad = a.get(i).doubleValue();
double bd = b.get(i).doubleValue();
if(!(Math.abs(ad - 2 * bd) < 0.0001)){
result = false;
}
}
return result;
}
No interface Collection
, do pacote java.util
, temos, por exemplo:
static void shuffle(List<?> list)
que baralha uma lista de elementos de qualquer tipo;static <T extends Comparable <? super T>> sort (List<T> list)
que ordena uma lista de elementos que se conseguem comparar com outros do mesmo tipo, ou seja, que implementam o método int compare(Q obj)
definido no interface Comparable<Q>
. A razão para que T
tenha que ser subtipo de Comparable <? super T>
é para ter a certeza que implementa o método compare
com um parâmetro do próprio tipo T
ou acima na hierarquia. Por exemplo, para que T
possa ser instanciado com o tipo JogoComObjetivo
, então JogoComObjetivo
tem que implementar o método compare
com um parâmetro do mesmo tipo ou do tipo Jogo
, por exemplo;static <T> sort(List<T> list, Comparator<? Super T> comp)
que faz o mesmo que o anterior, mas em vez de exigir que os elementos da lista se saibam comparar com outros do mesmo tipo, usa um comparador para os comparar;static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key)
que faz uma pesquisa binária numa lista já ordenada;static <T> T min(Collection<? extends T> coll, Comparator<? super T> comp)
que devolve o valor mínimo de uma lista, com o auxílio de um comparador.
Anterior: 15.6. Tipos genéricos e sub-tipos
Seguinte: 16. Classes abstratas