«Generic array creation» — erro que irrita todo desenvolvedor Java
\n\nVocê já escreveu algo como new T[10] e recebeu uma onda vermelha do compilador? Se sim, você não está sozinho. A pergunta «How can I create a generic array in Java?» no Stack Overflow acumulou centenas de milhares de visualizações. E não é por acaso: criar um array genérico em Java é um daqueles tópicos onde a teoria diverge da prática, e a IDE se recusa a entender o que você quer dela.
Neste artigo, vamos analisar por que o Java proíbe arrays genéricos, como contornar a limitação sem perder a segurança de tipos e quando o array ainda é melhor que ArrayList. Vamos lá!
Por que new T[10] não funciona? A raiz do mal — apagamento de tipos
\n\nPara entender como criar um array genérico em Java, primeiro é preciso compreender por que o compilador reclama. A questão é o type erasure (apagamento de tipos). Os genéricos do Java existem apenas em tempo de compilação. Durante a execução, a informação do tipo T é apagada e substituída por Object (ou pelo limite, se definido).
Já os arrays em Java são tipos reificáveis. Eles conhecem o tipo de seus componentes em tempo de execução. Portanto, a JVM pode verificar se você está colocando no array apenas elementos do tipo correto. Se você pudesse escrever new T[10], em tempo de execução o array seria Object[], e você poderia colocar String, Integer e Date simultaneamente — o que viola toda a ideia de segurança de tipos.
É por isso que o compilador gera o erro:
\n// ❌ Erro de compilação: generic array creation\npublic class Box<T> {\n private T[] array = new T[10]; // Cannot create a generic array of T\n}\n\nMétodo 1: O mais simples — use ArrayList
\n\nNa maioria das vezes, você nem precisa de um array. O ArrayList internamente trabalha com Object[], mas fornece uma interface segura quanto ao tipo. É a solução ideal se você não está obcecado com desempenho em nível de nanossegundos.
import java.util.ArrayList;\nimport java.util.List;\n\npublic class GenericListExample<T> {\n private List<T> list = new ArrayList<>();\n\n public void add(T item) {\n list.add(item);\n }\n\n public T get(int index) {\n return list.get(index);\n }\n\n public int size() {\n return list.size();\n }\n}\n\nVantagens: segurança total de tipos, sem avisos, suporte a expansão dinâmica.
\nDesvantagens: pequena sobrecarga de boxing/unboxing para primitivos (mas para 99% das aplicações isso é imperceptível).
Método 2: O truque clássico com (T[]) new Object[size]
\n\nSe você realmente precisa de um array (por exemplo, para desempenho ou compatibilidade com APIs legadas), pode usar um cast de tipos. Isso é seguro se você garantir que o array nunca saia dos limites da sua classe.
\n\npublic class Stack<T> {\n private T[] elements;\n private int size = 0;\n private static final int DEFAULT_CAPACITY = 10;\n\n @SuppressWarnings("unchecked")\n public Stack() {\n elements = (T[]) new Object[DEFAULT_CAPACITY];\n }\n\n public void push(T item) {\n if (size == elements.length) {\n resize();\n }\n elements[size++] = item;\n }\n\n public T pop() {\n if (size == 0) {\n throw new EmptyStackException();\n }\n T result = elements[--size];\n elements[size] = null; // Ajuda o GC\n return result;\n }\n\n private void resize() {\n elements = Arrays.copyOf(elements, elements.length * 2);\n }\n}\n\nImportante: a anotação @SuppressWarnings("uncheck