15  Generics e coleções

List, Set e Map com contratos de tipo

15.1 Objetivos de aprendizagem

Ao concluir este capítulo, você deverá ser capaz de:

  • escolher entre List, Set e Map;
  • explicar como generics evitam casts inseguros;
  • criar uma classe genérica simples;
  • registrar comandos por nome;
  • reconhecer coleções mutáveis e imutáveis.

15.2 Escolha da coleção

Ordem e repetição levam a List, unicidade leva a Set e busca por chave leva a Map.

A necessidade do problema orienta a escolha da coleção.
Estrutura Use quando
List<E> ordem e repetição importam
Set<E> elementos não podem se repetir
Map<K, V> um valor é localizado por uma chave

A interface descreve o contrato; a implementação define detalhes de desempenho e ordenação. Por exemplo, HashSet costuma oferecer busca eficiente, mas a interface Set sozinha não promete uma implementação específica.

15.3 Registro de comandos com Map

import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;

interface Comando {
    String nome();
    String executar();
}

final class RegistroComandos {
    private final Map<String, Comando> comandos = new LinkedHashMap<>();

    void registrar(Comando comando) {
        String chave = comando.nome().toLowerCase(Locale.ROOT);
        if (comandos.putIfAbsent(chave, comando) != null) {
            throw new IllegalArgumentException("Comando duplicado: " + chave);
        }
    }

    String executar(String entrada) {
        String chave = entrada.strip().toLowerCase(Locale.ROOT);
        Comando comando = comandos.get(chave);
        return comando == null
                ? "Comando desconhecido."
                : comando.executar();
    }
}

O mapa substitui a busca linear: o nome normalizado funciona como chave e o objeto polimórfico funciona como valor. Não é necessário introduzir Runnable; o contrato Comando expressa melhor o domínio.

15.4 Classe genérica

import java.util.ArrayList;
import java.util.List;

final class Repositorio<T> {
    private final List<T> itens = new ArrayList<>();

    void adicionar(T item) {
        itens.add(item);
    }

    List<T> listar() {
        return List.copyOf(itens);
    }
}

T representa um tipo escolhido por quem usa a classe. Repositorio<String> aceita textos; Repositorio<Comando> aceita comandos. O compilador impede misturas indevidas.

15.5 Exemplo completo

import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;

public class Capitulo14Generics {
    interface Comando {
        String executar();
    }

    private final Map<String, Comando> comandos = new LinkedHashMap<>();

    void registrar(String nome, Comando comando) {
        String chave = nome.toLowerCase(Locale.ROOT);
        if (comandos.putIfAbsent(chave, comando) != null) {
            throw new IllegalArgumentException("Comando duplicado.");
        }
    }

    String processar(String entrada) {
        Comando comando =
                comandos.get(entrada.strip().toLowerCase(Locale.ROOT));
        return comando == null ? "Comando desconhecido." : comando.executar();
    }

    public static void main(String[] args) {
        Capitulo14Generics registro = new Capitulo14Generics();
        registro.registrar("ajuda", () -> "Comandos disponíveis.");
        registro.registrar("dica", () -> "Use Map para localizar por chave.");

        System.out.println(registro.processar("DICA"));
    }
}

A expressão lambda implementa a única operação de Comando. Ela aparece aqui somente depois de o contrato ter sido estudado; uma classe nomeada continua válida quando o comportamento exigir estado ou explicação adicional.

List.of e Map.of criam coleções que não aceitam alterações. Use-as para dados fixos. Para registros que crescem durante a execução, escolha uma implementação mutável, como ArrayList ou LinkedHashMap.

O exemplo completo está em examples/src/Capitulo14Generics.java.

15.6 Prática verificável

  1. Rejeite nomes de comando vazios.
  2. Impeça duplicação de comandos.
  3. Crie um Set<String> com categorias de dicas.
  4. Use Repositorio<String> para armazenar perguntas de quiz.

Casos de teste:

  • registrar a mesma chave duas vezes lança exceção;
  • " DICA " encontra o comando dica;
  • comando desconhecido devolve a mensagem padrão;
  • a lista retornada pelo repositório não altera o estado interno.