Uma classe genérica é uma classe com um ou mais tipos de variáveis. Nesta seção, estou usando uma classe Objekt simples como um exemplo. Esta classe nos permite focalizarmos apenas no estudo de generics sem distração com os detalhes de armazenamento de dados. Segue abaixo o código da classe Objekt genérica:
public class Objekt<T>
{
private T var;
public Objekt()
{
this.var = null;
}
public Objekt(T var)
{
this.var = var;
}
public void setVariavel(T newvar)
{
this.var = newvar;
}
public T getVariavel()
{
return (var);
}
}
A classe Objekt apresenta um tipo de variável T, inclusa
dentro de parênteses de ângulo < >, depois do nome da classe. Uma classe
genérica pode ter mais de um tipo de variável. Por exemplo, eu poderia
definir a classe Objekt com tipos separados para o primeiro e segundo campos:
public class Objekt<T,K>
{
. . .
}
As variáveis de tipos são usadas dentro da classe para especificar os
tipos retornados pelos métodos, os tipos de parâmetros de entrada e variáveis locais da classe. Por exemplo:
private T var;
Você instancia os tipos genéricos substituindo os tipos para as variáveis
de tipo, como:
Objekt<String> objeto = new Objekt<String>("Viva o
Linux");
O resultado disto seria uma classe assim:
public class Objekt
{
private String var;
public Objekt()
{
this.var = null;
}
public Objekt(String var)
{
this.var = var;
}
public void setVariavel(String newvar)
{
this.var = newvar;
}
public String getVariavel()
{
return (var);
}
}
Também pode-se instanciar uma classe genérica como uma classe ordinária. Nesse caso o parâmetro passado a classe é a superclasse Object.
Objekt objeto = new Objekt();
Em resumo, uma classe genérica nada mais é do que uma fábrica de classe
ordinárias (comuns).
public class Test
{
public static void main(String args[])
{
Objetkt<String> program = new Objekt("Viva o Linux");
System.out.println(program.getVariavel());
}
}
Uma das mudanças no novo JDK 1.5 apresenta é que a classe
java.lang.Class é genérica. Este é um interessante exemplo da
capacidade de uso dos genéricos para qualquer tipo de container.
Como nem tudo são flores, as novas modificações de Java melhoram e muito a linguagem, mas existem algumas restrições e limitações nessas melhorias.
Infelizmente um a programação genérica tem uma observação, ela não pode ser utilizada para tipos primitivos. Isso quer dizer que só pode utilizar
dela par tipos objetos e seus derivados. Por exemplo, a classe abaixo vai
acusar um erro de compilação:
public class Teste<double>
{
...
}
Você não poderá declarar um array como uma variável de tipo, o código abaixo está completamente errado:
Objekt<Integer> array[] = new Objekt<Integer>(10);
Mas se você quer um conjunto de de variáveis de tipos, recomendo usar a classeArrayList. O código abaixo está correto:
ArrayList<Objekt<Integer>> array = new ArrayList<Objekt<integer>>();
Você não poderá inicializar tipos genéricos. Por exemplo, o construtor
abaixa está completamente ilegal:
public Objekt()
{
var = new T();
}
Similarmente, você não poderá criar um array de genéricos:
public <T> T[] getMax(T[] vetor)
{
T[] temp = new T[10];
...
}
Você não pode referenciar variáveis de tipo como campos estáticos.
Por exemplo, o código abaixo vai acusar um erro de compilação:
public class Primos<T>
{
private static T temp;
public static T createArray()
{
if(temp == null)
return temp;
}
}
O conceito de genéricos foi extendido não só a classes mas também para
métodos.
public <T> T getMiddle(T[] vetor)
{
return vetor[vetor.length / 2];
}
Note que este método é definido dentro de uma classe ordinária e não
dentro de uma classe genérica. Porém este é um método genérico, como você pode ver. Note que os tipos de variáveis são inseridas depois dos
modificadores e antes dos tipos de retorno.
Você pode definir métodos genéricos para ambos os casos, dentro de uma
classe ordinário ou dentro de uma classe genérica.
Quando você quiser chamar um método genérico você deve definir o tipo
dentro dos parênteses de ângulo e antes do nome do método:
String path = {"Viva","o ,"Linux"};
String middle = Test.<String>getMiddle(path);
Neste caso (e em muitos outros), você pode omitir os tipos de parâmetros
de entrada da chamada do método. O compilador já tem informação o bastante para deduzir que tipo de método você quer. Isto é, ele sabe que o tipo da variável path (neste caso, uma array de String)m compara com o tipo genérico T[] e deduz que é do tipo String. Você ainda pode simplificar ainda mais a chamada, como:
String middle = Test.getMiddle(path);
Mas algumas vezes os métodos precisam de uma restrição ao parâmetro que vai receber. No exemplo abaixo, o método compara dois parâmetros de entrada para verificar qual deles é o maior.
public static <T> T max(T x, T y, T z)
{
T result = x;
if(result.compareTo(y) > 0)
result = y;
if(result.compareTo(z) > 0)
result = z;
return (result);
}
Bem, o problema óbvio que existe é que nem todas as classes implementam a interface Comparable, logo o método compareTo() não existe nessas classes. Para que não ocasione em um erro de execução, deve-se impor limite para que o tipo de variável passada implemente a interface Comparable. Para isso utilizamos da palavra chave "extends", que nesse contexto indica que o objeto será um subtipo de Comparable.
public static <T extends Comparable> T max(T x, T y, T z)
{
T result = x;
if(result.compareTo(y) > 0)
result = y;
if(result.compareTo(z) > 0)
result = z;
return (result);
}
Aqui a palavra-chave "extends" é sobrecarregada, em lugar de ser criada uma nova palavra-chave. Neste código se mostra que o tipo T tem como "limite inferior" a classe Comparable.
Você pode ter mais de um limite para o tipo de parâmetro a ser passado.
Os limites de tipo devem ser separados pelo operador "&".
public static <T extends Comparable & Serializable> T max(T x, T y, T z)