Em muitos projetos Java (como os que você gerencia ou desenvolve), você provavelmente já se deparou com declarações como List<T>, Map<K,V>, ou até List<?> e ficou pensando: “O que exatamente cada letra representa?”. Vamos esclarecer.
Por que Java introduziu genéricos?
Antes do Java 5, coleções — como List, Map — operavam apenas com Object. Isso trazia duas dores principais:
- você podia colocar qualquer coisa ali, e só descobrir o tipo correto em tempo de execução, gerando
ClassCastException; - todo o código ficava cheio de casts explícitos, reduzindo legibilidade e aumentando o risco de erro.
Os genéricos vieram para resolver isso: permitir que classes, interfaces e métodos fossem parametrizados por tipo, trazendo segurança em tempo de compilação, menor necessidade de casting e código mais expressivo.
E o que significam esses símbolos?
Vamos ver os mais comuns e quando usá-los:
T – Type (ou qualquer “Tipo”)
Significa “um tipo genérico”. Por exemplo:
public class Box<T> {
private T value;
public void set(T v) { value = v; }
public T get() { return value; }
}
Aqui T representa “o tipo que o usuário vai passar”.
E – Element
Usado quando o genérico representa “elemento” de uma coleção. Por exemplo:
List<E>
Aqui o E indica “elemento do List”. É apenas uma convenção — você poderia usar T também, mas E melhora a leitura.
K, V – Key, Value
Usados normalmente em mapas:
Map<K, V>
K = tipo da chave, V = tipo do valor. Facilita entender “mapa de K para V”.
? – o curinga (wildcard)
Representa um tipo desconhecido. Exemplo:
List<?>
Significa “uma lista de algum tipo, não sei qual”. Útil para leitura genérica, quando você não precisa escrever elementos para ela.
Quando usar cada um — cenário prático
- Quando você cria uma classe ou método genérico que opera sobre um único tipo, use
T(ou outro nome significativo). - Quando você lida com “elementos de coleção”,
Efaz sentido. - Quando trabalha com pares ou mapas,
KeVsão ideais. - Quando você quer “algum tipo, mas não importa qual”, use
?(coringa). - E se seu método recebe ou retorna coleções de tipos que devem estar restritos (“subtipo de X” ou “supertipo de Y”), então entre em “bounded generics” com
extendsousuper.
Exemplos práticos para fixar
Simplificado:
public <T> void print(T t) {
System.out.println(t);
}
Aqui T = qualquer tipo.
Coleção de elementos:
public interface MyList<E> {
void add(E e);
E get(int index);
}
E = tipo do elemento da lista.
Mapa de chave-valor:
public interface MyMap<K, V> {
V get(K key);
void put(K key, V value);
}
K = tipo da chave, V = tipo do valor.
Uso de wildcard:
public void process(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
Aqui não importa qual o tipo contido na lista — só ler.
Por que essa convenção importa?
- Legibilidade: quando você vê
Map<K,V>, imediatamente entende o papel deKeV. - Manutenção: nomes padrão evitam confusão entre diferentes genéricos dentro de um código grande.
- Contratos de API: ao projetar bibliotecas ou APIs, usar
T,E,K,V,?de forma consistente facilita que outros desenvolvedores entendam e usem seu código.
Erros comuns ou armadilhas
- Usar
List<Object>quando você queriaList<?>— isso permite inserir qualquer tipo, perdendo parte da segurança. - Misturar
TeEsó para “parecer diferente” quando não há distinção de papel — isso pode confundir. - Não usar
? extends Tou? super Tem APIs mais flexíveis, perdendo a chance de oferecer mais reutilização (mas cuidado: isso exige bons conhecimentos de variância).
Conclusão
Os símbolos genéricos em Java (T, E, K, V, ?) são apenas convenções nomeadas — o que importa é o papel que o tipo genérico desempenha no contexto. Entender isso permite projetar APIs mais limpas, evitar erros de tipo e tornar seu código mais expressivo — essencial para quem trabalha com arquitetura limpa, padrões de projeto e código sustentável.





