Seu catálogo cresceu. Agora você precisa manter equipamentos, encontrar um deles pelo código e gerar uma lista dos disponíveis. Colocar tudo em uma ArrayList funciona para alguns requisitos, mas não responde automaticamente aos outros. Antes de decorar nomes, vamos escolher pelo comportamento necessário.
Esta aula pressupõe classes, interfaces, métodos e igualdade. Os exemplos usam Java 21. Ao final, você vai justificar uma estrutura, explicar o contrato dos seus elementos e construir uma transformação sem modificar dados por acidente.
A operação vem antes da implementação
Uma List mantém uma sequência e permite elementos repetidos. Uma Set representa elementos sem duplicação segundo o contrato de igualdade. Uma Map associa chaves a valores; cada chave tem no máximo um valor associado. Map participa do framework de coleções, mas não estende a interface Collection.
List, Set e Map são interfaces. ArrayList, HashSet e HashMap são implementações. Em List<String> nomes = new ArrayList<>();, o tipo da variável descreve o contrato que vamos usar; o objeto criado define a implementação. É perfeitamente válido declarar uma referência com tipo de interface.
Uma Deque oferece operações nas duas extremidades. Com ArrayDeque, podemos adicionar no fim e retirar do começo para representar uma fila, ou adicionar e retirar da mesma extremidade para representar uma pilha. Queue não significa sempre FIFO: uma fila de prioridade escolhe por prioridade, não necessariamente pela ordem de chegada.
Generics fazem o compilador ajudar
O <String> indica que a coleção trabalha com referências de strings. É um parâmetro de tipo. Se você tentar adicionar um inteiro, o compilador aponta incompatibilidade antes de executar. Não use o tipo cru List só para eliminar uma mensagem: você perde uma verificação útil.
Coleções genéricas não recebem tipos primitivos diretamente. List<Integer> usa a classe que representa valores inteiros; Java pode converter automaticamente entre int e Integer, processo chamado boxing/unboxing. Um Integer pode ser null; tentar convertê-lo para int nesse estado causa uma exceção.
List<Integer> não é subtipo de List<Number>. Se fosse, alguém poderia inserir um decimal na lista de inteiros. Um parâmetro List<? extends Number> permite ler elementos como números, mas não inserir um número arbitrário. Já List<? super Integer> aceita inteiros, mas a leitura mais geral é Object. Esses limites protegem a operação que o método promete realizar.
Um catálogo completo para observar
Salve em Catalogo.java:
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class Catalogo {
record Equipamento(String codigo, String nome, boolean disponivel) {}
public static void main(String[] args) {
Map<String, Equipamento> porCodigo = new LinkedHashMap<>();
porCodigo.put("C1", new Equipamento("C1", "Câmera", true));
porCodigo.put("T1", new Equipamento("T1", "Tripé", false));
porCodigo.put("L1", new Equipamento("L1", "Luz", true));
System.out.println(porCodigo.get("T1").nome());
List<String> disponiveis = new ArrayList<>();
for (Equipamento item : porCodigo.values()) {
if (item.disponivel()) {
disponiveis.add(item.nome());
}
}
disponiveis.sort(Comparator.naturalOrder());
System.out.println(disponiveis);
}
}
A saída é Tripé, seguida de [Câmera, Luz]. Usamos o código como chave e o objeto como valor. values() permite percorrer os valores. A lista do relatório é outra estrutura; ordená-la não reorganiza o mapa.
LinkedHashMap preserva ordem de inserção nesta configuração. HashMap não promete essa ordem. Ausência de garantia não significa que a ordem será sorteada a cada execução: significa que seu programa não deve depender dela.
put substitui o valor quando a chave já existe. Se o requisito é rejeitar duplicação, você precisa verificar e recusar, ou usar uma operação compatível como putIfAbsent e tratar seu resultado. Escolher Map não decide a política do negócio.
Igualdade e custo também fazem parte da escolha
Estruturas de hash usam hashCode e equals. Dois objetos iguais precisam ter hashes iguais; hashes iguais não obrigam objetos a serem iguais. Se um atributo usado nessa comparação muda enquanto o objeto é chave, uma busca pode deixar de encontrá-lo como você espera. Uma chave simples e imutável, como String, evita esse problema no catálogo.
Percorrer uma lista para buscar pelo código exige, no pior caso, examinar todos os elementos: custo O(n). Um HashMap busca normalmente em tempo esperado constante sob condições adequadas de distribuição dos hashes, com uso adicional de memória. Isso não significa “sempre mais rápido”, nem garante tempo igual em qualquer entrada.
Em ArrayList, acesso por índice é direto, mas remover no meio desloca elementos posteriores. Não escolha LinkedList supondo que toda inserção é barata: encontrar a posição pode exigir percurso. Complexidade descreve como o trabalho cresce; medir a aplicação continua importante.
Stream expressa uma transformação
Podemos substituir o bloco que monta e ordena o relatório por:
List<String> disponiveis = porCodigo.values().stream()
.filter(Equipamento::disponivel)
.map(Equipamento::nome)
.sorted()
.toList();
Esse trecho entra dentro de main, no lugar da criação e preenchimento da lista anterior. O resultado permanece [Câmera, Luz].
Um stream representa uma sequência de processamento; não é outra coleção de armazenamento. filter mantém elementos que passam no predicado, uma função que responde booleano. map transforma cada elemento. sorted ordena. toList é a operação terminal que materializa o resultado; a lista produzida por esse método não permite alterações estruturais.
Equipamento::nome é uma referência de método, equivalente aqui a item -> item.nome(). A expressão com seta é uma lambda: uma implementação curta de um comportamento exigido por uma interface funcional, interface com um único método abstrato.
Operações intermediárias descrevem a pipeline; o processamento é acionado por uma operação terminal. Um stream deve ser consumido uma vez. Para outro relatório, crie outro stream da fonte. Evite efeitos colaterais como adicionar a uma lista compartilhada dentro de map; descreva a transformação e recolha o resultado.
Ausência também precisa de um contrato
porCodigo.get("X9") retorna null quando não há valor nesse exemplo. Encadear .nome() causaria uma exceção. Já stream().findFirst() retorna um Optional: um objeto que representa presença ou ausência de resultado.
Você pode usar map e orElse("Nenhum disponível") para transformar um Optional e oferecer uma alternativa. Chamar get() sem verificar apenas desloca o problema. Optional não deve esconder erro de banco nem substituir toda propriedade anulável automaticamente; ele comunica uma decisão da API.
Agora escolha e explique
Você recebeu C1, C1, L1. Para preservar as três ocorrências na ordem, escolha List. Para guardar códigos únicos, escolha Set. Para localizar o equipamento completo pelo código, escolha Map. Nenhuma escolha é “a melhor coleção” sem requisito.
Modifique o catálogo para rejeitar um segundo C1. Depois produza o mesmo relatório com laço e stream. O critério é resultado igual e fonte preservada, não menor número de linhas. Se o relatório exigir atualização de diversos objetos e tratamento complexo de falhas, um laço claro pode ser preferível.
Na próxima revisão, explique sem consulta por que uma chave mutável é perigosa e por que List<Integer> não pode virar List<Number>. Esses contratos voltam quando os dados saem da memória e entram em arquivos, APIs e bancos.
Referência usada: guia de coleções, generics, records e Stream API.
Material complementar da Fernanda
- Guia intermediário: coleções, generics e Stream API — Use as seções de
List,Set,Map, generics e streams como consulta. Escolha uma estrutura para cada necessidade antes de copiar o exemplo.
Pratique com /aprender
Copie o comando e cole no chat para iniciar uma prática guiada:
/aprender Pratique List, Set, Map, generics e Stream API em Java 21 com um catálogo de produtos. Apresente uma necessidade concreta e peça que eu escolha a estrutura e preveja a saída de uma transformação; adapte a dificuldade à minha resposta, sem mostrar a solução antes.
