Introdução: O problema que os Generics resolvem
Imagine que você está escrevendo um método para ordenar uma lista de números. Você o escreve para Integer e, uma semana depois, precisa ordenar uma lista de String. É necessário criar um novo método ou usar Object e fazer conversões de tipo constantemente. Isso é inconveniente, inseguro e leva a erros em tempo de execução.
É exatamente esse problema que os Generics (genéricos) resolvem em Java. Eles permitem criar classes, interfaces e métodos que trabalham com diferentes tipos de dados, mas mantêm a tipagem estrita em tempo de compilação. Sem os genéricos, o Java moderno seria muito menos conveniente e seguro.
Neste artigo, vamos abordar para que servem os Generics, como eles funcionam e, o mais importante, como aplicá-los em exemplos reais.
1. Fundamentos dos Generics: o que são e como funcionam
1.1. O problema dos tipos "brutos" (raw types)
Antes do Java 5 (2004), os desenvolvedores usavam coleções sem especificar o tipo:
List list = new ArrayList();list.add("Hello");list.add(123);// Ao recuperar, é preciso lembrar qual tipo está armazenadoString text = (String) list.get(0); // OKInteger num = (Integer) list.get(1); // OK// Mas se errar — ClassCastException em runtimeString error = (String) list.get(1); // Erro!Esse código é perigoso: o erro só é descoberto quando o programa é executado.
1.2. Como os Generics funcionam
Os Generics permitem especificar o tipo de dados com o qual uma classe ou método irá trabalhar, diretamente nos colchetes angulares <T>. O compilador verifica os tipos em tempo de compilação e insere automaticamente as conversões corretas.
Sintaxe básica:
// Declaração de uma classe genéricapublic class Box<T> { private T content; public void setContent(T content) { this.content = content; } public T getContent() { return content; }}
// UsoBox<String> stringBox = new Box<>(); // <> — operador diamond (Java 7+)stringBox.setContent("Olá");String text = stringBox.getContent(); // Sem conversão!
Box<Integer> intBox = new Box<>();intBox.setContent(42);int number = intBox.getContent(); // Sem conversão!
// stringBox.setContent(123); // Erro de compilação!1.3. Vantagens na prática
- Segurança de tipos: erros são detectados em tempo de compilação, não em runtime.
- Eliminação de conversões explícitas: o código fica mais limpo e legível.
- Reutilização de código: uma classe/método funciona com qualquer tipo.
2. Generics na prática: classes, métodos e interfaces
2.1. Classes genéricas
O caso mais comum são as coleções. Mas você pode criar suas próprias classes genéricas:
public class Pair<K, V> { private K key; private V value; public Pair(K key, V value) { this.key = key; this.value = value; } public K getKey() { return key; } public V getValue() { return value; }}
// UsoPair<String, Integer> pair = new Pair<>("Idade", 30);String key = pair.getKey();Integer value = pair.getValue();2.2. Métodos genéricos
Um método pode ter seus próprios parâmetros de tipo, não relacionados à classe:
public class Utils { // Método genérico para criar uma lista a partir de um array public static <T> List<T> arrayToList(T[] array) { List<T> list = new ArrayList<>(); for (T element : array) { list.add(element); } return list; }}
// UsoString[] names = {"Ana", "Bruno", "Carlos"};List<String> nameList = Utils.arrayToList(names);