CodeGym /Cursos /JAVA 25 SELF /Set: HashSet e TreeSet, unicidade dos elementos

Set: HashSet e TreeSet, unicidade dos elementos

JAVA 25 SELF
Nível 26 , Lição 3
Disponível

1. Introdução

Vamos começar com um exemplo do dia a dia. Imagine que você está organizando uma festa e montando a lista de convidados. Você envia os convites e, depois, descobre que a mesma pessoa apareceu na lista duas vezes (ou até três — ele realmente adora festas!). Se você usar uma lista comum (List), essas duplicatas podem aparecer facilmente. Mas se você tivesse uma coleção que simplesmente não permitisse adicionar o mesmo convidado duas vezes — a vida ficaria muito mais simples.

É aqui que entra em cena a coleção Set — um conjunto.

Set é uma coleção que armazena apenas elementos únicos. Se você tentar adicionar um elemento que já existe, ele simplesmente não será adicionado (e ninguém vai ficar chateado).

Interface Set: propriedades básicas

Em Java, Set é uma interface que define o comportamento de uma coleção sem duplicatas. Ela herda da interface Collection, o que significa que suporta operações como adicionar (add), remover (remove), verificar se um elemento existe (contains) e percorrer.

Principais características:

  • Em Set não podem existir dois elementos iguais.
  • Os elementos podem ser armazenados em ordem arbitrária — depende da implementação concreta.
  • Não há índices: não é possível acessar um elemento pelo número, como em uma lista.

Sintaxe de declaração

Set<String> guests = new HashSet<>();

2. HashSet: rápido, simples, sem ordem

HashSet é a implementação mais popular da interface Set. Ela é baseada em uma tabela hash (assim como HashMap, só que sem o par “chave-valor”, apenas com valores únicos). A principal vantagem é a rapidez das operações de adição, remoção e busca.

Como o HashSet funciona?

Imagine uma caixa onde você guarda coisas. Para descobrir rapidamente se já existe algo parecido nela, cada item recebe seu “número” — o hash code. Quando você adiciona um elemento ao HashSet, primeiro é calculado esse hash code. Se tal código ainda não apareceu, o elemento é colocado na coleção. Se o hash já existe, a igualdade é verificada adicionalmente por meio de equals(). E somente se os objetos realmente coincidirem, o novo elemento não é adicionado.

Ou seja, o HashSet cuida automaticamente da unicidade: dois objetos idênticos não aparecerão nele.

Ponto interessante: se você trabalha com suas próprias classes e quer armazená-las em um HashSet, será necessário sobrescrever os métodos equals() e hashCode(). Sem isso, a coleção pode se comportar de forma imprevisível — objetos aparentemente iguais podem ser considerados diferentes.

Métodos principais do HashSet

Set<String> guests = new HashSet<>();

guests.add("Ivan");
guests.add("Maria");
guests.add("Pyotr");
guests.add("Ivan"); // Duplicata! Não será adicionada.

System.out.println(guests); // [Ivan, Maria, Pyotr] — a ordem pode ser qualquer uma

guests.remove("Pyotr"); // Removemos o elemento
System.out.println(guests.contains("Maria")); // true
System.out.println(guests.size()); // 2

Vamos testar isso no código

Suponha que, no nosso aplicativo, queremos armazenar nomes únicos de tarefas, para que não haja duas tarefas com o mesmo título:

import java.util.HashSet;
import java.util.Set;

public class UniqueTasksDemo {
    public static void main(String[] args) {
        Set<String> tasks = new HashSet<>();
        tasks.add("Fazer a lição de casa de Java");
        tasks.add("Fazer carinho no gato");
        tasks.add("Fazer a lição de casa de Java"); // Duplicata!

        System.out.println("Lista de tarefas:");
        for (String task : tasks) {
            System.out.println("- " + task);
        }
        // Na lista haverá apenas duas tarefas; a duplicata não será adicionada
    }
}

3. TreeSet: a ordem importa!

Às vezes, precisamos não apenas de unicidade, mas também de um conjunto ordenado de elementos. Por exemplo, queremos ver os nomes dos convidados em ordem alfabética, e não em ordem aleatória. Para isso existe o TreeSet.

TreeSet é uma implementação da interface Set que armazena os elementos em ordem crescente. Ele é baseado na estrutura “árvore rubro-negra”.

Exemplo de uso do TreeSet

import java.util.Set;
import java.util.TreeSet;

public class SortedGuestsDemo {
    public static void main(String[] args) {
        Set<String> guests = new TreeSet<>();
        guests.add("Vladimir");
        guests.add("Alexey");
        guests.add("Ekaterina");
        guests.add("Alexey"); // Duplicata!

        System.out.println("Convidados (em ordem alfabética):");
        for (String guest : guests) {
            System.out.println("- " + guest);
        }
        // Saída:
        // - Alexey
        // - Vladimir
        // - Ekaterina
    }
}

Atenção: Se você adicionar uma duplicata, ela não aparecerá no conjunto. Tudo como deveria ser!

Quando usar TreeSet?

  • Quando você precisa de um conjunto ordenado de elementos únicos.
  • Quando a busca rápida é importante, mas a velocidade de inserção não é crítica (funciona um pouco mais devagar que o HashSet).
  • Se os elementos são suas próprias classes, eles devem ser “comparáveis” (implementar a interface Comparable) ou você deve fornecer um Comparator próprio.

4. Nuances úteis

HashSet vs TreeSet: qual escolher?

Critério HashSet TreeSet
Ordem de armazenamento Não é garantida Ordenado em ordem crescente
Velocidade das operações Mais rápido (O(1)) Mais lento (O(log n))
Requisitos do tipo Qualquer um (basta equals()/hashCode()) Comparable ou Comparator
Cenários típicos Quando é necessário acesso rápido a elementos únicos Quando é importante uma saída/percurso ordenado

Particularidades ao trabalhar com Set

  • Sem índices. Diferentemente de List, Set não possui o método get(int index). Se precisar de acesso por índice — use List.
  • Sem duplicatas. Se você tentar adicionar um elemento que já existe, ele não será adicionado. O método add retornará false.
  • Ordem não garantida (exceto TreeSet). Em HashSet, a ordem dos elementos pode variar a cada execução. Se precisar manter a ordem de inserção, use LinkedHashSet.
  • Valores null.
    • HashSet permite armazenar um elemento null.
    • TreeSet não permite adicionar null sem um Comparator especial; caso contrário, ocorrerá NullPointerException.

5. Tarefas típicas com Set

Remoção de duplicatas de uma lista

Suponha que temos uma lista de estudantes, na qual alguns aparecem duas vezes. Precisamos manter apenas os nomes únicos:

import java.util.*;

public class RemoveDuplicatesDemo {
    public static void main(String[] args) {
        List<String> students = Arrays.asList("Anna", "Igor", "Anna", "Maria", "Igor", "Pavel");

        Set<String> uniqueStudents = new HashSet<>(students);

        System.out.println("Estudantes únicos: " + uniqueStudents);
        // A ordem não é garantida!
    }
}

Se precisar de um resultado ordenado — use TreeSet:

Set<String> sortedUniqueStudents = new TreeSet<>(students);
System.out.println("Estudantes únicos (em ordem alfabética): " + sortedUniqueStudents);

Verificação de unicidade (por exemplo, login de usuário)

Set<String> usedLogins = new HashSet<>();
usedLogins.add("student1");
usedLogins.add("java_lover");

String newLogin = "student1";
if (usedLogins.contains(newLogin)) {
    System.out.println("Esse login já está em uso!");
} else {
    System.out.println("Login disponível!");
}

Iteração dos elementos de um conjunto

A iteração é feita com o loop for-each:

for (String name : uniqueStudents) {
    System.out.println(name);
}

6. Erros comuns ao trabalhar com Set

Erro nº 1: esperar uma ordem específica de elementos em um HashSet. Muitos iniciantes se surpreendem com o “estranho” ordenamento dos elementos. Isso é normal — HashSet não garante ordem. Se precisar da ordem de inserção — use LinkedHashSet; se precisar de ordenação — TreeSet.

Erro nº 2: tentar acessar um elemento por índice. Às vezes tentam escrever algo como set.get(0). Não é possível: Set não suporta indexação. Precisa de acesso por índice? Use List.

Erro nº 3: armazenar objetos mutáveis. Se você armazena objetos cujos campos que participam de equals()/hashCode() podem mudar, após a alteração desses campos o elemento pode “se perder” para o conjunto. Torne os elementos imutáveis ou não altere os campos identificadores.

Erro nº 4: esperar que duplicatas sejam adicionadas. Adicionar o mesmo elemento várias vezes não aumentará o tamanho do conjunto — duplicatas são ignoradas, o método add retornará false.

Erro nº 5: uso de tipos primitivos. Uma declaração como Set<int> não compilará. Use as classes wrapper: Set<Integer>, Set<Double> etc.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION