Genéricos em Java: como parar de ter medo de colchetes angulares e começar a escrever código confiável
\n\nNo mundo Java, há uma coisa que assusta os iniciantes quase mais do que a concorrência — são os colchetes angulares <>. Generics (também conhecidos como genéricos ou generalizações) parecem misteriosos, mas na verdade são seu principal aliado na luta por um código limpo e seguro. Vamos entender como funciona a tipagem em Java com exemplos que você pode aplicar hoje mesmo.
Imagine: você está escrevendo um método que deve funcionar com números, strings e suas próprias classes. Sem genéricos, você teria que criar dezenas de métodos sobrecarregados ou usar Object e verificar tipos manualmente constantemente. As generalizações Java resolvem esse problema de forma elegante e segura.
Por que sem genéricos o código se transforma em um campo minado?
\n\nVamos lembrar como os programadores viviam antes do surgimento dos generics (antes do Java 5). Se você já escreveu um ArrayList sem especificar o tipo, conhece esse pesadelo:
List list = new ArrayList();\nlist.add("Olá");\nlist.add(42);\nlist.add(new Object());\n\n// Em algum lugar após 100 linhas de código:\nString text = (String) list.get(1); // ClassCastException em tempo de execução!\n\nO problema clássico: ClassCastException — um erro que aparece apenas durante a execução. Você poderia passar a lista para outro método, e alguém lá está certo de que ela contém apenas strings. E bum! O aplicativo quebra. Os genéricos em Java transferem a verificação de tipos para o tempo de compilação — é como um seguro que funciona antes de você pegar o volante.
\n\nGenerics em Java: sintaxe básica que você precisa saber
\n\nNa base dos genéricos está uma ideia simples: parametrizamos uma classe ou método com um tipo. Em vez de um tipo concreto, escrevemos um parâmetro de tipo (geralmente uma letra maiúscula: T, E, K, V).
O exemplo mais popular são as coleções Java:
\n\n// Sem genérico — perigoso\nList rawList = new ArrayList();\n\n// Com genérico — seguro\nList<String> safeList = new ArrayList<>();\nsafeList.add("Java");\nsafeList.add("Generics");\n// safeList.add(123); // Erro de compilação!\n\nO compilador agora sabe: dentro de safeList só podem haver strings. Tentar adicionar um número causará um erro em tempo de compilação, não em tempo de execução. Isso é a segurança de tipos Java em ação.
Criando sua primeira classe genérica
\n\nSuponha que estamos escrevendo um contêiner simples para armazenar um valor. Sem genéricos, teríamos que usar Object:
public class Box {\n private Object value;\n\n public void set(Object value) { this.value = value; }\n public Object get() { return value; }\n}\n\nAgora — com genérico:
\n\npublic class Box<T> {\n private T value;\n\n public void set(T value) { this.value = value; }\n public T get() { return value; }\n}\n\n// Uso:\nBox<Integer> intBox = new Box<>();\nintBox.set(100);\nint value = intBox.get(); // Sem cast!\n\nObserve: não precisamos mais escrever (Integer) intBox.get(). O compilador sabe que get() retornará Integer. Essa é a magia das generalizações Java.
Tipagem em Java: restrições e wildcards
\n\nOs genéricos seriam muito simples se não houvesse nuances. A principal delas são os bounded type parameters (parâmetros de tipo limitados). Às vezes, precisamos dizer: "O tipo T deve ser um número" ou "T deve implementar a interface Comparable".
\n\n// T só pode ser Number ou suas subclasses\npublic class Calculator<T extends Number> {\n private T value;\n\n public double square() {\n return value.doub