O que são generics?
Source: Dev.to
Generics são uma funcionalidade introduzida no Java 5 que permite criar classes, interfaces e métodos que trabalham com diferentes tipos de dados. Eles eliminam a necessidade de conversões explícitas (casts) e ajudam a detectar erros em tempo de compilação.
Antes do Java 5, as coleções tratavam tudo como Object. Isso gerava dois problemas:
- Casting excessivo: era preciso fazer cast
(String)toda vez que se retirava algo da lista. - Insegurança: nada impedia a inserção de um
Integernuma lista que deveria conter apenasString, resultando em erro em tempo de execução.
// Código antigo
List lista = new ArrayList(); // Lista "crua" (Raw Type)
lista.add("Olá");
lista.add(10); // O compilador deixa passar!
// O erro só acontece aqui, quando o programa está rodando:
String texto = (String) lista.get(1); // ClassCastException!
// Código novo com Generics
List lista = new ArrayList<>();
lista.add("Olá");
// lista.add(10); // O compilador nem deixa compilar. Erro na hora!
String texto = lista.get(0); // Sem necessidade de cast manual.
Variáveis de tipo
Generics permitem que classes e métodos operem em objetos de vários tipos, garantindo a segurança em tempo de compilação (compile‑time safety). Por convenção, usamos letras maiúsculas para representar esses tipos genéricos:
T: Type (tipo genérico)E: Element (usado em collections)K,V: Key, Value (usado em maps)
Classe Genérica
Uma classe genérica utiliza um parâmetro de tipo representado por T, que funciona como um placeholder para o tipo real.
// define que esta classe aceita um parâmetro de tipo
public class Caixa<T> {
private T conteudo;
public void guardar(T conteudo) {
this.conteudo = conteudo;
}
public T abrir() {
return conteudo;
}
}
public class AulaGenerics {
public static void main(String[] args) {
// Caixa que só aceita Strings
Caixa<String> caixaDeTexto = new Caixa<>();
caixaDeTexto.guardar("Segredo");
// caixaDeTexto.guardar(123); // Erro de compilação!
// Caixa que só aceita Inteiros
Caixa<Integer> caixaDeNumeros = new Caixa<>();
caixaDeNumeros.guardar(100);
}
}
T é substituído por String, Integer ou outro tipo no momento da criação do objeto.
Wildcards
Os wildcards (?) representam tipos desconhecidos em generics e são úteis para criar métodos que aceitam diferentes tipos de coleções.
?– qualquer tipo.? extends T(upper bounded) – aceitaTou qualquer subtipo deT. Uso: quando se quer apenas LER dados.? super T(lower bounded) – aceitaTou qualquer supertipo deT. Uso: quando se quer ESCREVER dados.
Exemplo Prático
import java.util.*;
public class Wildcards {
// Aceita lista de Number OU QUALQUER SUBTIPO (Integer, Double...)
// Só consigo LER com segurança (sei que tudo ali é Number)
public static double somarLista(List lista) {
double soma = 0;
for (Number n : lista) { // Posso ler como Number
soma += n.doubleValue();
}
// lista.add(10); // ERRO! Não posso adicionar nada (não sei se é lista de Double ou Integer)
return soma;
}
public static void main(String[] args) {
List inteiros = new ArrayList<>();
inteiros.add(1);
inteiros.add(2);
List doubles = new ArrayList<>();
doubles.add(1.5);
doubles.add(2.5);
// O método aceita ambos!
System.out.println(somarLista(inteiros));
System.out.println(somarLista(doubles));
}
}
Type Erasure
Generics existem apenas para o compilador. Após a compilação, o Java apaga (type erasure) todas as informações de generics e as substitui por Object (ou pelo limite superior) inserindo casts automaticamente. Em tempo de execução, List e List são a mesma classe (List). Essa estratégia foi adotada para manter compatibilidade com versões anteriores do Java.