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:

  1. O compilador javac valida todos os tipos genéricos estritamente em compilação.
  2. Em seguida, o compilador remove todas as informações de tipo genérico no arquivo .class, substituindo os parâmetros formais T por seu limite superior (Object ou pela interface declarada, ex: <T extends Comparable<T>> torna-se Comparable).
  3. 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 usar obj 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()

  1. Se obj1.equals(obj2) for true, obrigatoriamente obj1.hashCode() == obj2.hashCode().
  2. Se dois objetos têm o mesmo hashCode(), eles não são necessariamente iguais (trata-se de uma colisão de hash).
  3. Nunca altere campos usados no hashCode() após inserir o objeto em um HashSet ou como chave de HashMap, 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("================================================================================");
    }
}