Generics em Java

Generics foram introduzidos no Java 5 para fornecer segurança de tipo em tempo de compilação e eliminar a necessidade de conversões explícitas. Eles permitem que você defina classes, interfaces e métodos com tipos parametrizados, proporcionando um código mais reutilizável e robusto. Neste artigo, exploraremos como usar generics em Java, cobrindo desde os conceitos básicos até usos avançados como wildcards.
Subtipos
Em Java, a subtipagem com generics não funciona exatamente como se poderia esperar. Por exemplo, List<String>
não é um subtipo de List<Object>
. Isso ocorre porque, ao permitir isso, a segurança de tipo seria comprometida. Veja um exemplo:
List<String> stringList = new ArrayList<>();
// List<Object> objList = stringList; // Isso não compila
Entretanto, arrays genéricos seguem uma lógica diferente:
Object[] objArray = new String[10]; // Isso compila
No entanto, esse comportamento pode levar a exceções em tempo de execução:
Object[] objArray = new String[10];
objArray[0] = 10; // Lança ArrayStoreException em tempo de execução
Usando Generics em Classes e Interfaces
Definir uma classe ou interface genérica é bastante simples. Aqui está um exemplo de uma classe genérica:
public class Caixa<T> {
private T conteudo;
public void setConteudo(T conteudo) {
this.conteudo = conteudo;
}
public T getConteudo() {
return conteudo;
}
}
Você pode usar esta classe com diferentes tipos de dados:
Caixa<String> caixaString = new Caixa<>();
caixaString.setConteudo("Olá, Mundo!");
System.out.println(caixaString.getConteudo());
Caixa<Integer> caixaInteger = new Caixa<>();
caixaInteger.setConteudo(123);
System.out.println(caixaInteger.getConteudo());
Da mesma forma, podemos criar interfaces genéricas:
public interface Par<K, V> {
K getChave();
V getValor();
}
Usando Generics em Métodos e Construtores
Além de classes e interfaces, métodos também podem ser genéricos. Aqui está um exemplo de um método genérico:
public class Util {
public static <T> void imprimirArray(T[] array) {
for (T elemento : array) {
System.out.print(elemento + " ");
}
System.out.println();
}
}
Você pode chamar este método com arrays de diferentes tipos:
Integer[] arrayInt = {1, 2, 3, 4, 5};
String[] arrayStr = {"A", "B", "C", "D"};
Util.imprimirArray(arrayInt);
Util.imprimirArray(arrayStr);
Construtores genéricos também são possíveis, embora menos comuns:
public class Par<K, V> {
private K chave;
private V valor;
public <T> Par(T chave, T valor) {
this.chave = (K) chave;
this.valor = (V) valor;
}
// Getters...
}
Wildcards
Wildcards (?
) são usados em generics para representar um tipo desconhecido. Existem três tipos principais de wildcards:
Wildcard não restrito
Representa qualquer tipo:
public void imprimirLista(List<?> lista) {
for (Object elem : lista) {
System.out.print(elem + " ");
}
System.out.println();
}
Wildcard com restrição superior
Restringe o tipo a ser um subtipo de um tipo específico:
public void imprimirListaNumeros(List<? extends Number> lista) {
for (Number num : lista) {
System.out.print(num + " ");
}
System.out.println();
}
Wildcard com restrição inferior
Restringe o tipo a ser um supertipo de um tipo específico:
public void adicionarNumeros(List<? super Integer> lista) {
lista.add(1);
lista.add(2);
lista.add(3);
}
Conclusão
Generics são uma poderosa funcionalidade em Java, proporcionando maior flexibilidade e segurança de tipo. Compreender como e quando usar generics pode melhorar significativamente a qualidade do seu código. Desde a definição de classes e métodos genéricos até o uso de wildcards, generics oferecem uma maneira eficiente de lidar com tipos em Java. Experimente aplicar generics em seus projetos para aproveitar ao máximo esses benefícios.