Capítulo 13: Generics Avançados, Wildcards Delimitados, e Coleções Set e Map
Especialização em Sistemas com Java 21 LTS • Módulo de Recursos Avançados e Concorrência
🗺️ Mapa Conceitual do Tópico
flowchart TD
subgraph GENERICS["Generics & Type Safety"]
A["Type Safety em Tempo de Compilação"]
B["Type Erasure no Bytecode (javac apaga T para Object)"]
C["Invariância: List~String~ NÃO é List~Object~"]
A --> B & C
end
subgraph PECS["Princípio PECS (Wildcards Delimitados)"]
D["Producer Extends: ? extends T (Leitura / Covariante)"]
E["Consumer Super: ? super T (Escrita / Contravariante)"]
end
subgraph COLECOES["Ecossistema de Conjuntos e Mapas"]
F["Contrato Obrigatório: hashCode() & equals()"]
G["HashMap / HashSet: Buckets + Árvore Rubro-Negra O(1)"]
H["TreeMap / TreeSet: Árvore Binária Balanceada O(log n)"]
I["LinkedHashMap / LinkedHashSet: Preserva Ordem / LRU"]
F --> G & H & I
end
C --> PECS
PECS --> COLECOES
style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style D fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style E fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style G fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
🧬 1. Fundamentos de Generics e o Mecanismo de Type Erasure
Introduzidos no Java 5, os Generics garantem segurança estrita de tipos em tempo de compilação (Compile-time Type Safety), eliminando a necessidade de coerções manuais de tipo (type casting) suscetíveis a ClassCastException.
O Mecanismo de Type Erasure (Apagamento de Tipos):
Para manter compatibilidade binária retroativa com código legado Java 1.4:
- O compilador
javacvalida todos os tipos genéricos estritamente em compilação. - Em seguida, o compilador remove todas as informações de tipo genérico no arquivo
.class, substituindo os parâmetros formaisTpor seu limite superior (Objectou pela interface declarada, ex:<T extends Comparable<T>>torna-seComparable). - O compilador insere casts automáticos no bytecode onde necessário.
Consequências Práticas do Type Erasure:
- Não é possível instanciar o tipo genérico:
new T()resulta em erro de compilação. - Não é possível criar arrays genéricos diretamente:
new T[10]é proibido. - Não é possível testar o tipo concreto via
instanceof:obj instanceof List<String>é ilegal (deve-se usarobj instanceof List<?>).
🦅 2. O Princípio PECS (Producer Extends, Consumer Super)
Em Java, os tipos genéricos são invariantes: embora Integer seja subclasse de Number, uma List<Integer> não é um subtipo de List<Number>. Se isso fosse permitido, poderíamos inserir um Double em uma lista originalmente de inteiros, quebrando o sistema de tipos.
Para permitir flexibilidade polimórfica em bibliotecas, Joshua Bloch formalizou a regra PECS:
1. Producer Extends (? extends T - Covariância)
Utilize quando a coleção for atuar como uma produtora de dados para o seu método (você apenas lê elementos dela).
// Permite passar List<Integer>, List<Double>, List<BigDecimal>...
public static double somarValores(List<? extends Number> lista) {
double total = 0.0;
for (Number n : lista) {
total += n.doubleValue(); // Leitura segura
}
// lista.add(10); // ERRO DE COMPILAÇÃO! Não é permitido inserir nada além de null
return total;
}
2. Consumer Super (? super T - Contravariância)
Utilize quando a coleção for atuar como uma consumidora de dados (você insere elementos nela).
// Permite passar List<Number>, List<Object>...
public static void popularComZeros(List<? super Integer> lista, int quantidade) {
for (int i = 0; i < quantidade; i++) {
lista.add(0); // Escrita segura
}
// Object obj = lista.get(0); // Apenas leitura genérica como Object é permitida
}
🗂️ 3. A Estrutura Interna de Set e Map
Conjuntos (Set) e Mapas (Map) são estruturas de associação chave-valor e unicidade fundamentais:
O Contrato Sagrado: hashCode() e equals()
- Se
obj1.equals(obj2)fortrue, obrigatoriamenteobj1.hashCode() == obj2.hashCode(). - Se dois objetos têm o mesmo
hashCode(), eles não são necessariamente iguais (trata-se de uma colisão de hash). - Nunca altere campos usados no
hashCode()após inserir o objeto em umHashSetou como chave deHashMap, sob risco de tornar o elemento inacessível na tabela de buckets.
Mecânica Interna do HashMap:
- Tabela de Buckets: Array interno inicial de tamanho 16 (
Node<K,V>[] table). O índice é calculado via(n - 1) & hash(key). - Tratamento de Colisões: Colisões formam uma lista encadeada no bucket.
- Treefication (Java 8+): Quando um mesmo bucket acumula mais de 8 elementos e a tabela possui pelo menos 64 buckets, a lista encadeada é convertida em uma Árvore Rubro-Negra (Red-Black Tree), reduzindo o pior caso de busca de $O(n)$ para $O(\log n)$.
📊 4. Matriz Comparativa: Famílias Set e Map
| Estrutura | Ordenação dos Elementos | Custo de Busca/Inserção | Aceita Chave Nula? | Estrutura Subjacente |
|---|---|---|---|---|
HashMap / HashSet |
Nenhuma (caótica / por hash) | $O(1)$ | Sim (1 chave nula no bucket 0) | Tabela Hash com Buckets e Árvores |
LinkedHashMap / LinkedHashSet |
Ordem de inserção ou de acesso | $O(1)$ | Sim | Tabela Hash + Lista Duplamente Encadeada |
TreeMap / TreeSet |
Ordem natural ou Comparator |
$O(\log n)$ | Não (lança NPE) | Árvore Rubro-Negra balanceada (Red-Black) |
💻 5. Código de Demonstração Corporativo (Java 21 LTS)
package com.empresa.colecoes;
import java.math.BigDecimal;
import java.util.*;
/**
* Sistema de cache LRU corporativo e consolidação com Generics PECS.
*/
public class GestorCatalogoGenerico {
public record Produto(String sku, String nome, BigDecimal preco) implements Comparable<Produto> {
@Override
public int compareTo(Produto outro) {
return this.sku.compareTo(outro.sku);
}
}
// Cache LRU (Least Recently Used) de tamanho fixo com LinkedHashMap
public static class CacheLru<K, V> extends LinkedHashMap<K, V> {
private final int capacidadeMaxima;
public CacheLru(int capacidadeMaxima) {
super(capacidadeMaxima, 0.75f, true); // true = ordem de acesso
this.capacidadeMaxima = capacidadeMaxima;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > capacidadeMaxima; // Remove o mais antigo quando excede o limite
}
}
// Método utilitário aplicando PECS: Copia de produtor (extends) para consumidor (super)
public static <T> void copiarColecao(List<? extends T> origem, List<? super T> destino) {
for (T elemento : origem) {
destino.add(elemento);
}
}
public static void main(String[] args) {
System.out.println("================================================================================");
System.out.println(" GENÉRICS AVANÇADOS, PECS E ESTRUTURAS DE DADOS (JAVA 21) ");
System.out.println("================================================================================");
// 1. Demonstração de Cache LRU
CacheLru<String, Produto> cache = new CacheLru<>(3);
cache.put("SKU-1", new Produto("SKU-1", "Mouse Sem Fio", new BigDecimal("120.00")));
cache.put("SKU-2", new Produto("SKU-2", "Teclado Mecânico", new BigDecimal("450.00")));
cache.put("SKU-3", new Produto("SKU-3", "Monitor 24pol", new BigDecimal("980.00")));
System.out.printf("Cache Inicial (Tamanho %d): %s%n", cache.size(), cache.keySet());
// Acessando SKU-1 (torna-o recentemente usado)
cache.get("SKU-1");
// Adicionando SKU-4 (deve expulsar SKU-2, que foi o menos recentemente usado)
cache.put("SKU-4", new Produto("SKU-4", "Headset Gamer", new BigDecimal("350.00")));
System.out.printf("Cache Após Acesso e Novo Item (Expulsou o mais antigo): %s%n", cache.keySet());
// 2. Demonstração de PECS
List<Produto> loteNovo = List.of(
new Produto("SKU-A", "Tablet Pro", new BigDecimal("2500.00")),
new Produto("SKU-B", "Smartwatch", new BigDecimal("1200.00"))
);
List<Object> historicoGlobal = new ArrayList<>();
copiarColecao(loteNovo, historicoGlobal); // List<Produto> (? extends T) -> List<Object> (? super T)
System.out.printf("%nRegistros copiados para o histórico de auditoria global: %d itens%n", historicoGlobal.size());
System.out.println("================================================================================");
}
}