Visão geral do framework de coleções
O Java Collections Framework é um conjunto de interfaces e classes para armazenar, organizar e manipular grupos de objetos. A escolha da estrutura adequada impacta diretamente legibilidade, corretude (por exemplo, lidar com duplicados) e performance (tempo e memória).
| Necessidade | Estrutura recomendada | Por quê |
|---|---|---|
| Manter ordem de inserção e acessar por índice | List (ex.: ArrayList) | Permite get(i) e mantém sequência |
| Evitar duplicados | Set (ex.: HashSet) | Garante unicidade via equals/hashCode |
| Chave → valor (dicionário) | Map (ex.: HashMap) | Busca por chave eficiente |
| Manter elementos ordenados | TreeSet/TreeMap | Ordenação natural ou por Comparator |
Interfaces fundamentais: Collection, List, Set e Map
Collection
Collection é a interface base para estruturas que representam “um conjunto de elementos” (com operações como add, remove, contains, size). Ela é estendida por List e Set. Map não estende Collection porque representa pares chave-valor.
List
List representa uma sequência ordenada, permite elementos duplicados e oferece acesso por índice. Use quando a posição importa (ex.: ranking, fila de tarefas, histórico).
ArrayList: ótima para leitura e acesso por índice; inserções/remoções no meio podem ser custosas por deslocamento de elementos.LinkedList: boa para muitas inserções/remoções nas extremidades; acesso por índice é mais lento (precisa percorrer nós).
Set
Set representa um conjunto sem duplicados. A noção de “duplicado” depende de equals e, em estruturas baseadas em hash, também de hashCode.
HashSet: não garante ordem; operações típicas são muito rápidas (média O(1)).TreeSet: mantém elementos ordenados; operações típicas O(log n); exige ordenação natural (Comparable) ouComparator.
Map
Map armazena pares chave → valor. Chaves são únicas; valores podem repetir. É ideal para indexação e contagens.
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
HashMap: não garante ordem; busca/inserção média O(1).TreeMap: mantém chaves ordenadas; operações O(log n); útil quando você precisa percorrer em ordem.
Generics na prática: evitando casts e erros em runtime
Generics permitem declarar o tipo dos elementos armazenados, garantindo checagem em tempo de compilação e eliminando a necessidade de casts.
Exemplo: List sem generics (evite)
import java.util.*; class Demo { public static void main(String[] args) { List lista = new ArrayList(); lista.add("Ana"); lista.add(10); // mistura tipos, compila List<String> nomes = lista; // warning e pode quebrar em runtime } }Exemplo: List com generics (recomendado)
import java.util.*; class Demo { public static void main(String[] args) { List<String> nomes = new ArrayList<>(); nomes.add("Ana"); // nomes.add(10); // erro de compilação } }O mesmo vale para Set<T> e Map<K, V>. Em Map, declare sempre os dois tipos: Map<String, Integer>, por exemplo.
Escolhendo a implementação: critérios práticos
ArrayList vs LinkedList
- Se você faz muitas leituras (
get) e iterações:ArrayListtende a ser melhor. - Se você insere/remove frequentemente no início (ou mantém uma fila/deque):
LinkedListpode ser uma opção, mas muitas vezesArrayListainda é suficiente; avalie com base no padrão real de uso.
HashSet vs TreeSet
- Se você só precisa de unicidade e rapidez:
HashSet. - Se precisa manter ordenado (ex.: exibir em ordem alfabética):
TreeSet.
HashMap vs TreeMap
- Se a prioridade é performance de acesso por chave:
HashMap. - Se você precisa iterar pelas chaves em ordem:
TreeMap.
Iteração: for-each e Iterator (e quando usar cada um)
for-each (mais simples e legível)
import java.util.*; class Demo { public static void main(String[] args) { List<String> nomes = List.of("Ana", "Bia", "Caio"); for (String n : nomes) { System.out.println(n); } } }Use for-each quando você não precisa remover elementos durante a iteração e não precisa do índice.
Iterator (necessário para remover com segurança)
Remover elementos de uma coleção enquanto itera com for-each costuma causar ConcurrentModificationException. Para remoção durante a iteração, use Iterator e iterator.remove().
import java.util.*; class Demo { public static void main(String[] args) { List<String> nomes = new ArrayList<>(List.of("Ana", "", "Bia", "")); Iterator<String> it = nomes.iterator(); while (it.hasNext()) { String atual = it.next(); if (atual.isBlank()) { it.remove(); } } System.out.println(nomes); } }Iterando Map: entrySet é o caminho mais eficiente
import java.util.*; class Demo { public static void main(String[] args) { Map<String, Integer> freq = new HashMap<>(); freq.put("java", 3); freq.put("maven", 1); for (Map.Entry<String, Integer> e : freq.entrySet()) { System.out.println(e.getKey() + " => " + e.getValue()); } } }entrySet() evita buscas repetidas por chave (como ocorreria com keySet() + get).
Regras de igualdade: equals/hashCode e impacto em Set/Map
HashSet e HashMap dependem de duas regras: (1) se a.equals(b) é true, então a.hashCode() deve ser igual a b.hashCode(); (2) hashCode deve ser estável enquanto o objeto estiver “imutável” do ponto de vista dos campos usados no cálculo.
Problema comum: objeto mutável usado como chave
Se você usa um objeto como chave em HashMap e depois altera um campo que participa de equals/hashCode, a chave pode “sumir” (o mapa não consegue mais encontrá-la no bucket correto).
Exemplo prático com classe de domínio
import java.util.*; class Produto { private final String sku; private final String nome; Produto(String sku, String nome) { this.sku = sku; this.nome = nome; } public String getSku() { return sku; } public String getNome() { return nome; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Produto)) return false; Produto other = (Produto) o; return Objects.equals(this.sku, other.sku); } @Override public int hashCode() { return Objects.hash(sku); } } class Demo { public static void main(String[] args) { Set<Produto> set = new HashSet<>(); set.add(new Produto("SKU-1", "Teclado")); set.add(new Produto("SKU-1", "Teclado Gamer")); // considerado duplicado pelo sku System.out.println(set.size()); } }Neste exemplo, a unicidade é definida por sku. Isso é intencional: escolha quais campos definem identidade e mantenha consistência.
Ordenação e comparação: TreeSet/TreeMap e Comparator
TreeSet e TreeMap ordenam elementos/chaves. Você pode usar a ordem natural (implementando Comparable) ou fornecer um Comparator.
Ordenando Strings por tamanho (Comparator)
import java.util.*; class Demo { public static void main(String[] args) { Set<String> palavras = new TreeSet<>(Comparator.comparingInt(String::length).thenComparing(Comparator.naturalOrder())); palavras.addAll(List.of("java", "jdk", "collections", "map", "set")); System.out.println(palavras); } }Em TreeSet, dois elementos que o Comparator considere “iguais” (comparação retorna 0) não coexistem. Portanto, o critério de ordenação também afeta a noção de duplicidade.
Passo a passo: remover duplicados, contar frequências, ordenar e filtrar
1) Remover duplicados preservando a ordem de aparição
Quando você quer remover duplicados mas manter a ordem original, uma estratégia simples é usar um Set auxiliar para “marcar” o que já apareceu.
import java.util.*; class Demo { public static void main(String[] args) { List<String> entrada = List.of("java", "maven", "java", "jdk", "maven"); Set<String> vistos = new HashSet<>(); List<String> semDuplicados = new ArrayList<>(); for (String item : entrada) { if (vistos.add(item)) { // add retorna false se já existia semDuplicados.add(item); } } System.out.println(semDuplicados); } }Alternativa: new LinkedHashSet<>(lista) remove duplicados e preserva ordem, mas você perde a estrutura List (pode converter de volta depois).
2) Contar frequências com Map
Use Map<T, Integer> para contar ocorrências. O método getOrDefault simplifica a lógica.
import java.util.*; class Demo { public static void main(String[] args) { List<String> palavras = List.of("java", "java", "jdk", "maven", "java", "maven"); Map<String, Integer> freq = new HashMap<>(); for (String p : palavras) { int atual = freq.getOrDefault(p, 0); freq.put(p, atual + 1); } System.out.println(freq); } }Se você precisa do resultado ordenado por chave, troque para TreeMap ao final: Map<String, Integer> ordenado = new TreeMap<>(freq);.
3) Ordenar resultados por frequência (desc) e filtrar
Uma forma prática é transformar entrySet() em uma lista e ordenar com Comparator. Depois, filtre por um critério (por exemplo, frequência mínima).
import java.util.*; class Demo { public static void main(String[] args) { Map<String, Integer> freq = new HashMap<>(); freq.put("java", 3); freq.put("maven", 2); freq.put("jdk", 1); List<Map.Entry<String, Integer>> entradas = new ArrayList<>(freq.entrySet()); entradas.sort(Comparator.<Map.Entry<String, Integer>>comparingInt(Map.Entry::getValue).reversed().thenComparing(Map.Entry::getKey)); int minimo = 2; for (Map.Entry<String, Integer> e : entradas) { if (e.getValue() >= minimo) { System.out.println(e.getKey() + ": " + e.getValue()); } } } }Exercícios propostos
Exercício 1: remover duplicados
Dada uma List<String> com nomes (com repetição), gere uma nova lista sem duplicados preservando a ordem original. Requisitos: (1) não use estruturas de ordenação; (2) explique por que HashSet ajuda; (3) compare com a alternativa LinkedHashSet.
Exercício 2: contador de palavras
Receba uma lista de palavras (simulando tokens já separados) e produza um Map<String, Integer> com frequências. Depois, imprima as palavras em ordem alfabética. Dica: conte com HashMap e ordene com TreeMap no final.
Exercício 3: top N por frequência
Com o mapa de frequências do exercício anterior, gere uma lista de entradas ordenada por frequência decrescente e, em caso de empate, por chave crescente. Imprima apenas as N primeiras (por exemplo, N=3). Dica: use entrySet(), ArrayList e Comparator.
Exercício 4: filtragem e remoção segura
Dada uma List<String> com possíveis strings vazias ou em branco, remova-as durante a iteração sem causar erro. Requisito: use Iterator e remove().
Exercício 5: equals/hashCode na prática
Crie uma classe Aluno com matricula e nome. Coloque objetos em um HashSet e garanta que alunos com a mesma matrícula sejam considerados duplicados. Requisitos: implemente equals e hashCode com base em matricula e demonstre com um teste simples de tamanho do conjunto.