1. O problema das coleções “brutas” (raw types)
Vamos mergulhar rapidamente na história. Antes do Java 5, todas as coleções eram “onívoras”. Elas armazenavam objetos do tipo Object, e o compilador não controlava o que exatamente você colocava nelas. Quer colocar uma string? Por favor. Um número? Por que não. Um gato? Também pode.
// Exemplo de coleções "brutas" (raw types), Java antes da versão 5
List list = new ArrayList();
list.add("Olá");
list.add(42);
list.add(new Object());
O problema aparecia ao “retirar” e usar o valor:
String s = (String) list.get(0); // OK, é uma String
String s2 = (String) list.get(1); // BOOM! ClassCastException
O compilador fica em silêncio, e em tempo de execução você recebe ClassCastException. É como uma caixa com a etiqueta “maçãs” que contém uma xícara, uma banana e um ouriço.
Por que isso é ruim?
- Os erros só aparecem em tempo de execução.
- Tipos se misturam: é preciso converter objetos manualmente (cast).
- O código fica menos legível e mais perigoso.
A solução — generics (genéricos)
Generics (genéricos) são um mecanismo que permite criar classes, interfaces e métodos com parâmetros de tipo. Ou seja, você diz à coleção: “Armazene apenas strings”, e o compilador fiscaliza isso rigorosamente.
List<String> words = new ArrayList<>();
words.add("Olá");
words.add("Mundo");
// words.add(42); // Erro de compilação! Não é possível adicionar int a List<String>
Agora o compilador não vai permitir colocar na lista nada além de strings. O erro é detectado antes de executar o programa.
Ideia principal dos generics:
Garantir a segurança de tipos das coleções (e não só), para que os erros sejam capturados na fase de compilação, e não em tempo de execução.
2. Sintaxe de generics: como isso fica no código
Indicação do tipo entre os sinais “<>”
Ao criar uma coleção, indique o tipo dos elementos em <>:
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
// names.add(123); // Erro: não é possível adicionar um número a uma lista de strings
String first = names.get(0); // Não é necessário cast!
Clássicos:
- List<String> — lista de strings
- List<Integer> — lista de inteiros
- Set<Double> — conjunto de números de ponto flutuante
- Map<String, Integer> — chave String, valor Integer
Por que não escrever apenas List?
Pode, mas você perde todos os benefícios dos generics, e o compilador vai avisar:
List list = new ArrayList(); // raw type — não recomendado!
list.add("Hello");
list.add(7.5);
String s = (String) list.get(1); // Olá, ClassCastException!
Código Java moderno sempre usa generics.
Operador diamante <>
Desde o Java 7, você pode não especificar o tipo à direita se ele for óbvio pelo contexto:
List<String> list = new ArrayList<>(); // O compilador deduz que é <String> aqui
3. Generics para diferentes coleções
Exemplos para List, Set, Map
List<Integer> numbers = new ArrayList<>();
numbers.add(10);
numbers.add(20);
Set<String> uniqueNames = new HashSet<>();
uniqueNames.add("Alice");
uniqueNames.add("Bob");
Map<String, Integer> ages = new HashMap<>();
ages.put("Alice", 23);
ages.put("Bob", 31);
Exemplo com classe própria
class Student {
String name;
int age;
// ...
}
List<Student> students = new ArrayList<>();
students.add(new Student());
4. Nuances úteis
Vantagens dos generics
Segurança de tipos. O compilador garante que apenas elementos do tipo correto entrem na coleção.
Não há necessidade de cast. Antes: String s = (String) list.get(0);. Agora: String s = list.get(0);.
Código mais legível e confiável. Menos surpresas em tempo de execução.
Limitações dos generics
Não é possível usar tipos primitivos. Generics funcionam apenas com objetos, não com primitivos (int, double, boolean). Use classes wrapper: Integer, Double, Boolean.
List<Integer> numbers = new ArrayList<>();
numbers.add(10); // int é convertido automaticamente para Integer (autoboxing)
Breve sobre apagamento de tipos (type erasure)
No Java, os generics são implementados por meio do mecanismo de apagamento de tipos: após a compilação, a informação sobre parâmetros de tipo é apagada, e em tempo de execução a JVM não sabe que aquilo era List<String>, e não simplesmente List. Isso foi feito por compatibilidade retroativa.
Consequência: não é possível verificar o parâmetro de tipo com instanceof usando um argumento de tipo específico.
List<String> list = new ArrayList<>();
// if (list instanceof List<String>) { ... } // Erro de compilação!
Tentar adicionar elemento de outro tipo — erro de compilação
List<String> words = new ArrayList<>();
words.add("Hello");
// words.add(123); // Erro de compilação: incompatible types: int cannot be converted to String
Map<String, Integer> map = new HashMap<>();
map.put("Gato", 5);
// map.put(3, "Elefante"); // Erro: a chave deve ser String, o valor — Integer
E isso é ótimo: os erros são capturados na fase de compilação.
Não apenas coleções
Generics podem ser usados em suas próprias classes e métodos. Por exemplo, uma “Caixa” genérica:
class Box<T> {
private T value;
public void set(T value) { this.value = value; }
public T get() { return value; }
}
Box<String> stringBox = new Box<>();
stringBox.set("Olá");
System.out.println(stringBox.get());
Box<Integer> intBox = new Box<>();
intBox.set(42);
System.out.println(intBox.get());
Em coleções, os generics são padrão, mas você também os encontrará em outros lugares, como no Stream API e em Optional.
5. Erros comuns ao trabalhar com generics
Erro №1: uso de coleções “brutas”. Uma declaração como List list = new ArrayList(); remove a segurança de tipos. Sempre indique os parâmetros de tipo, por exemplo List<String>.
Erro №2: tentativa de usar primitivos. Não é possível escrever List<int>; use List<Integer>.
Erro №3: cast manual ao ler da coleção. Se você usa generics, o cast do tipo (String) list.get(i) não é necessário. Se for preciso — em algum lugar os tipos foram violados.
Erro №4: esperar que os parâmetros de tipo estejam disponíveis em tempo de execução. Devido ao apagamento de tipos, não é possível verificá-los com instanceof como em List<String>.
Erro №5: misturar tipos diferentes em uma mesma coleção. Se foi declarado List<String>, não tente adicionar Integer — o compilador não permitirá, e isso é bom.
GO TO FULL VERSION