1. Operação union (união de conjuntos)
Em Java, para representar conjuntos usa-se a interface Set<T>. Diferentemente de listas (List), conjuntos garantem a unicidade dos elementos e, em geral, não se preocupam com a ordem (a menos que você use implementações especiais, como LinkedHashSet). As implementações mais populares — HashSet e TreeSet. Sua principal tarefa — determinar rapidamente a presença de um elemento e garantir a ausência de duplicatas.
Quando usar conjuntos?
- Quando a unicidade é importante: por exemplo, a lista de todos os usuários únicos que visitaram o site.
- Quando é preciso verificar rapidamente a presença de um elemento: o método contains em HashSet geralmente funciona em tempo constante.
- Quando for necessário executar operações típicas sobre coleções: união, interseção, diferença.
Union — é a união de dois ou mais conjuntos: o resultado contém todos os elementos de ambos os conjuntos de origem (sem repetições).
Exemplo na prática
Suponha que temos dois conjuntos de alunos que frequentam os clubes “Robótica” e “Programação”:
Set<String> robotics = Set.of("Anya", "Boris", "Vika");
Set<String> programming = Set.of("Vika", "Gleb", "Dasha");
Precisamos obter o conjunto de todos os alunos que frequentam pelo menos um clube.
Solução com Stream API
A forma mais direta — concatenar ambos os streams e coletá-los em um Set:
Set<String> all = Stream.concat(
robotics.stream(),
programming.stream()
).collect(Collectors.toSet());
System.out.println(all); // [Anya, Boris, Vika, Gleb, Dasha]
Explicação:
- Stream.concat concatena dois streams.
- collect(Collectors.toSet()) coleta os elementos em um conjunto (removendo duplicatas automaticamente).
Alternativa: mais de dois conjuntos
Se tivermos três ou mais clubes, use Stream.of e flatMap:
Set<String> math = Set.of("Zhenya", "Vika", "Boris");
Set<String> all = Stream.of(robotics, programming, math)
.flatMap(Set::stream)
.collect(Collectors.toSet());
System.out.println(all); // [Anya, Boris, Vika, Gleb, Dasha, Zhenya]
Por que Set?
Porque Set remove duplicatas automaticamente. Se você coletar em uma List, os mesmos nomes aparecerão várias vezes.
2. Operação intersection (interseção de conjuntos)
Intersection — são os elementos que existem simultaneamente em ambos os conjuntos.
Exemplo na prática
Encontrar alunos que frequentam tanto “Robótica” quanto “Programação”:
Set<String> robotics = Set.of("Anya", "Boris", "Vika");
Set<String> programming = Set.of("Vika", "Gleb", "Dasha");
Solução com Stream API:
Set<String> both = robotics.stream()
.filter(programming::contains)
.collect(Collectors.toSet());
System.out.println(both); // [Vika]
Explicação:
Percorremos todos os participantes de “Robótica” e filtramos apenas aqueles que estão em “Programação”. O resultado é um conjunto com os nomes que aparecem em ambos os clubes.
Alternativa (sem Stream API)
É possível usar o método incorporado retainAll (ele modifica o conjunto atual):
Set<String> intersection = new HashSet<>(robotics);
intersection.retainAll(programming);
System.out.println(intersection); // [Vika]
Mas, no tema de Stream API, focamos nos streams.
3. Operação difference (diferença de conjuntos)
Difference — são os elementos do primeiro conjunto que não existem no segundo.
Exemplo na prática
Encontrar alunos que frequentam somente “Robótica”, mas não “Programação”:
Set<String> robotics = Set.of("Anya", "Boris", "Vika");
Set<String> programming = Set.of("Vika", "Gleb", "Dasha");
Solução com Stream API:
Set<String> onlyRobotics = robotics.stream()
.filter(name -> !programming.contains(name))
.collect(Collectors.toSet());
System.out.println(onlyRobotics); // [Anya, Boris]
Explicação:
Filtramos os participantes de “Robótica”, deixando apenas aqueles que não estão em “Programação”.
Alternativa (sem Stream API)
Set<String> difference = new HashSet<>(robotics);
difference.removeAll(programming);
System.out.println(difference); // [Anya, Boris]
4. Tarefas práticas: processamento de listas de usuários
Tarefa 1: Encontrar alunos que frequentam apenas um clube
Precisamos saber quem frequenta apenas “Robótica” ou apenas “Programação”, mas não ambos ao mesmo tempo. Isso é a diferença simétrica (xor para conjuntos):
Set<String> onlyOne = Stream.concat(
robotics.stream().filter(name -> !programming.contains(name)),
programming.stream().filter(name -> !robotics.contains(name))
).collect(Collectors.toSet());
System.out.println(onlyOne); // [Anya, Boris, Gleb, Dasha]
Tarefa 2: Lista de todos os alunos únicos de vários clubes
Set<String> all = Stream.of(robotics, programming, math)
.flatMap(Set::stream)
.collect(Collectors.toSet());
System.out.println(all); // [Anya, Boris, Vika, Gleb, Dasha, Zhenya]
Tarefa 3: Encontrar alunos que não frequentam nenhum clube
Suponha que temos a lista de todos os alunos da turma:
Set<String> allStudents = Set.of("Anya", "Boris", "Vika", "Gleb", "Dasha", "Zhenya", "Igor’", "Katya");
Precisamos saber quem não frequenta nenhum clube:
Set<String> attendees = Stream.of(robotics, programming, math)
.flatMap(Set::stream)
.collect(Collectors.toSet());
Set<String> notInAny = allStudents.stream()
.filter(name -> !attendees.contains(name))
.collect(Collectors.toSet());
System.out.println(notInAny); // [Igor’, Katya]
5. Observações importantes: equals, hashCode e desempenho
Por que é importante implementar corretamente equals e hashCode?
Todas as operações com conjuntos (Set) dependem da correção dos métodos equals e hashCode. Se você armazena objetos de sua própria classe (por exemplo, Student), então é obrigatório sobrescrever esses métodos; caso contrário, as comparações não funcionarão corretamente.
Exemplo:
class Student {
String name;
int age;
// Não se esqueça de sobrescrever equals e hashCode!
}
Se isso não for feito, dois alunos com os mesmos nomes e idades serão considerados objetos diferentes para o Set.
Por que é melhor usar Set, e não List?
- A operação contains em Set é rápida (geralmente, tempo constante).
- Em List, a busca de um elemento ocorre em tempo linear, o que pode ser crítico para coleções grandes.
- Para operações sobre conjuntos (union, intersection, difference), Set é muito mais eficiente e lógico.
6. Erros comuns ao trabalhar com operações sobre conjuntos
Erro nº 1: Usar List em vez de Set para operações sobre conjuntos. Se você coleta elementos em uma List, as duplicatas não são removidas, e a operação contains é lenta. Para union/intersection/difference, use Set.
Erro nº 2: equals/hashCode não implementados para objetos. Se você armazena em um Set objetos de sua própria classe, mas não sobrescreveu os métodos equals e hashCode, então a interseção e a diferença se comportarão “estranhamente” — objetos que, em essência, são iguais não serão considerados iguais.
Erro nº 3: Modificar a coleção durante o stream. Se você tentar modificar o Set original diretamente dentro do stream (por exemplo, adicionando ou removendo elementos), você terá um ConcurrentModificationException. Sempre trabalhe com um novo conjunto.
Erro nº 4: Perda de ordem não óbvia. HashSet não garante a ordem dos elementos. Se a ordem for importante — use LinkedHashSet ou TreeSet.
Erro nº 5: Usar Stream.concat para mais de duas coleções. Stream.concat concatena apenas dois streams. Para uma quantidade maior, use Stream.of(...) e flatMap.
Erro nº 6: Problemas com null. Conjuntos não gostam de valores null, especialmente se você usar Set.of(...) — ele não permite null. Para lidar com null, use outras implementações ou filtre os valores antecipadamente.
GO TO FULL VERSION