Java/Genéricos
Genéricos, também conhecidos como Tipos Genéricos, são parâmetros de tipos em classes, métodos e interfaces. É possível definir tipos genéricos para as variáveis de referência dentro de um determinado escopo (classes, interfaces e métodos) e ao realizar uma chamada ao método, ou definir um variável de referência e/ou instanciar um construtor os tipos genéricos são substituídos pelo tipo especificado.
Com a utilização de genéricos é possível abstrair o tipo de parâmetro e/ou de retorno de métodos, o que garante maior reaproveitamento de código.
Parâmetro de tipo
editarComo visto no capítulo sobre Collections é possível definir o tipo do elemento que será armazenado e recuperado de uma coleção ao especificar-se na variável entre um par de chevrons, <
e >
, o tipo desejado. Esse tipo utilizado nas variáveis da lista de parâmetros e no tipo de retorno dos métodos é chamado de tipo genérico.
Na documentação java.util.ArrayList
há as seguintes assinaturas de método que utilizam o tipo genérico E.
public boolean add(E e)
public E get(int index)
É convencionado o identificador E para elementos, K para chaves, V para valores e T para tipos, porém não há obrigatoriedade no uso. Pode-se utilizar qualquer identificador válido no parâmetro de tipo.
Parâmetro de tipo é o tipo genérico definido entre o par de chevrons na definição de uma classe ou interface. Deve ser inserido após o identificador da classe.
No código abaixo temos a definição da classe java.util.ArrayList
e da interface java.util.List
, ambas com o parâmetro de tipo definidos com o tipo genérico E.
//Declarações parciais para evidenciar apenas os conceitos apresentados.
class ArrayList<E> {...}
interface List<E> {...}
Argumento de tipo é o tipo especificado, entre o par de chevrons, na declaração de um tipo (chamada de construtor ou variável de referência) que substituirá o tipo genérico.
No código abaixo o tipo genérico E foi substituído, em tempo de compilação, pelo argumento de tipo String. Com isso o tipo da variável na lista de parâmetros do método boolean add(E e)
foi alterado de E e
para String e
e o tipo de retorno do método E get(int index)
foi de E
para String
.
List<String> lista = new ArrayList<String>();
lista.add("Elemento");
String elemento = lista.get(0);
System.out.println(elemento);
Elemento
Para declarar-se classe ou interface com referências de tipos genéricos, basta especificar o tipo a ser substituído no parâmetro de tipo.
class Objeto<T> {
T objeto;
T getObjeto() {
return objeto;
}
void setObjeto(T objeto) {
this.objeto = objeto;
}
}
Ao utilizar-se uma classe ou interface que utiliza tipos genéricos basta enviar no argumento de tipo o tipo desejado. No código abaixo String
foi definido como o tipo na variável de referência (Objeto<String> obj
) e na chamada do construtor (new Objeto<String>()
).
Objeto<String> obj = new Objeto<String>();
obj.setObjeto("Uma string qualquer.");
System.out.println(obj.getObjeto());
Uma string qualquer.
Pode-se definir mais de um tipo de parâmetro. Para tal, é necessário separá-los com vírgula e não utilizar o mesmo tipo já utilizado.
class Par<K, T> {
K chave;
T objeto;
Par(K chave, T objeto){
this.chave = chave;
this.objeto = objeto;
}
T getObjeto() {
return objeto;
}
K getChave() {
return chave;
}
void setObjeto(T objeto) {
this.objeto = objeto;
}
}
class Teste {
public static void main(String[] args) {
Par<Integer, String> par = new Par<Integer, String>(42, "É a pergunta?");
par.setObjeto("é a resposta para o universo.");
System.out.print(par.getChave() + " " + par.getObjeto() + "\n");
}
}
42 é a resposta para o universo.
A partir do Java SE 7 pode-se utilizar o operador diamante, que nada mais é que o argumento de tipo sem tipo especificado na chamada do construtor. Em tempo de compilação o argumento de tipo do construtor será inserido em correspondência ao argumento de tipo do tipo da variável.
Par<Integer, String> teste = new Par<>();
É possível definir uma classe ou interface com tipos genéricos em parâmetros de tipo.
Par<Integer, Objeto<String>> par = new Par<>(52, new Objeto<>());
par.getObjeto().setObjeto("objeto");
System.out.println(par.getChave() + ": " + par.getObjeto().getObjeto() + "\n");
52: objeto
É possível repassar o parâmetro de tipo definido em uma classe como argumento de tipo para referências de tipo genérico. No código abaixo a classe Colecao foi definida com o tipo T em seu parâmetro de tipo. O tipo T é então utilizado como argumento de tipo da variável de referência List<T> lista
.
class Colecao<T> {
List<T> lista;
List<T> getLista() {
return lista;
}
void setLista(List<T> lista) {
this.lista = lista;
}
}
class Teste {
public static void main(String[] args) {
Colecao<Double> colecao = new Colecao<>();
colecao.setLista(Arrays.asList(5.4, 3.43));
System.out.println(colecao.getLista());
}
}
[5.4, 3.43]
Parâmetro de tipo limitado
editarÉ possível substituir T por java.lang.Object
nos códigos da subseção anterior, logo, a única vantagem é a eliminação de cast no retorno de uma chamada ao método getObjeto.
Um benefício maior na utilização de Genéricos se dá ao especificar-se uma restrição aos tipos de parâmetro possíveis. É possível obter essa restrição definindo T como subclasse de determinada classe ou como implementador de determinada interface com a utilização da instrução extends
.
No código abaixo, T é restringido à CharSequence ou suas classes implementadoras.
class Teste<T extends CharSequence> {
public static void main(String[] args) {
Teste<String> teste = new Teste<>();
String palavra = teste.metodo("Exemplo");
System.out.println(palavra);
}
T metodo (T t) {
System.out.println(t.subSequence(0, 2));
return t;
}
}
Ex Exemplo
Caso utilizado um tipo diferente na linha 7, como metodo(5);
teria-se o erro de compilação abaixo.
Teste.java:7: error: method metodo in class Teste cannot be applied to given types; String palavra = metodo(5); ^ required: T found: int reason: inferred type does not conform to upper bound(s) inferred: Integer upper bound(s): CharSequence where T is a type-variable: T extends CharSequence declared in method <T>metodo(T) 1 error
Inferência de tipo em métodos
editarAlternativa à inferência de tipo no parâmetro de tipo da classe, é possível definir um parâmetro de tipo apenas para um método.
Para isso basta inserir o parâmetro de tipo antes do tipo de retorno.
<T> void metodo () {}
No código abaixo o tipo genérico, definido como T
, é utilizado no tipo de retorno e no tipo do parâmetro na lista de parâmetros da declaração de método. Em tempo de compilação, o tipo T
será convertido no tipo do argumento do método chamador, no caso abaixo como o argumento do método é do tipo String
("Exemplo") logo o tipo definido será String
.
class Teste {
public static void main(String[] args) {
String palavra = Teste.metodo("Exemplo");
System.out.println(palavra);
}
static <T> T metodo (T t) {
return t;
}
}
Exemplo
Tipo Cru
editarPara manter-se a compatibilidade com versões anteriores ao Java SE 5, a partir do lançamento da referida versão foi permitido que argumentos de tipo possam ser omitidos, são os chamados Tipos Crus.
//Tipo parametrizado.
Set<Double> conjunto;
//Tipo cru.
Set conjunto;
Em tempo de compilação os tipos crus são convertidos para o tipo Object
.
List lista = new ArrayList();
lista.add(5.5);
int i = (int) lista.get(0);
System.out.println(i);
5.5
Caso não houvesse o cast (int)
na linha 9 o seguinte erro seria exibido.
Teste.java:9: error: incompatible types: Object cannot be converted to int int i = lista.get(0); ^ Note: Teste.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details.