1. Operazione union (unione degli insiemi)
In Java, per rappresentare gli insiemi si usa l’interfaccia Set<T>. A differenza delle liste (List), gli insiemi garantiscono l’unicità degli elementi e, di norma, non si preoccupano dell’ordine (a meno di usare implementazioni specifiche, per esempio LinkedHashSet). Le implementazioni più diffuse — HashSet e TreeSet. Il loro compito principale — determinare rapidamente la presenza di un elemento e garantire l’assenza di duplicati.
Quando servono gli insiemi?
- Quando è importante l’unicità: ad esempio, l’elenco di tutti gli utenti unici che hanno visitato il sito.
- Quando serve verificare rapidamente la presenza di un elemento: il metodo contains di HashSet in genere funziona in tempo costante.
- Quando bisogna eseguire operazioni tipiche sugli insiemi: unione, intersezione, differenza.
Union è l’unione di due o più insiemi: il risultato contiene tutti gli elementi di entrambi gli insiemi di partenza (senza ripetizioni).
Esempio pratico
Supponiamo di avere due insiemi di studenti che frequentano i club «Robotica» e «Programmazione»:
Set<String> robotics = Set.of("Anya", "Boris", "Vika");
Set<String> programming = Set.of("Vika", "Gleb", "Dasha");
Dobbiamo ottenere l’insieme di tutti gli studenti che frequentano almeno un club.
Soluzione con la Stream API
Il modo più diretto è concatenare i due stream e raccoglierli in un Set:
Set<String> all = Stream.concat(
robotics.stream(),
programming.stream()
).collect(Collectors.toSet());
System.out.println(all); // [Anya, Boris, Vika, Gleb, Dasha]
Spiegazione:
- Stream.concat concatena due stream.
- collect(Collectors.toSet()) raccoglie gli elementi in un insieme (eliminando automaticamente i duplicati).
Alternativa: più di due insiemi
Se abbiamo tre o più club, usiamo 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]
Perché proprio Set?
Perché Set elimina automaticamente i duplicati. Se si raccoglie in una List, gli stessi nomi compariranno più volte.
2. Operazione intersection (intersezione degli insiemi)
Intersection sono gli elementi presenti contemporaneamente in entrambi gli insiemi.
Esempio pratico
Trovare gli studenti che frequentano sia «Robotica» sia «Programmazione»:
Set<String> robotics = Set.of("Anya", "Boris", "Vika");
Set<String> programming = Set.of("Vika", "Gleb", "Dasha");
Soluzione con la Stream API:
Set<String> both = robotics.stream()
.filter(programming::contains)
.collect(Collectors.toSet());
System.out.println(both); // [Vika]
Spiegazione:
Passiamo su tutti i partecipanti a «Robotica» e filtriamo solo quelli che sono in «Programmazione». Il risultato è l’insieme dei nomi presenti in entrambi i club.
Metodo alternativo (senza Stream API)
Si può usare il metodo incorporato retainAll (modifica l’insieme corrente):
Set<String> intersection = new HashSet<>(robotics);
intersection.retainAll(programming);
System.out.println(intersection); // [Vika]
Ma nell’ambito della Stream API poniamo l’accento sugli stream.
3. Operazione difference (differenza tra insiemi)
Difference sono gli elementi del primo insieme che non sono presenti nel secondo.
Esempio pratico
Trovare gli studenti che frequentano solo «Robotica» e non «Programmazione»:
Set<String> robotics = Set.of("Anya", "Boris", "Vika");
Set<String> programming = Set.of("Vika", "Gleb", "Dasha");
Soluzione con la Stream API:
Set<String> onlyRobotics = robotics.stream()
.filter(name -> !programming.contains(name))
.collect(Collectors.toSet());
System.out.println(onlyRobotics); // [Anya, Boris]
Spiegazione:
Filtriamo i partecipanti a «Robotica», lasciando solo quelli che non sono in «Programmazione».
Metodo alternativo (senza Stream API)
Set<String> difference = new HashSet<>(robotics);
difference.removeAll(programming);
System.out.println(difference); // [Anya, Boris]
4. Esercizi pratici: elaborazione di elenchi di utenti
Esercizio 1: trovare gli studenti che frequentano un solo club
Bisogna scoprire chi frequenta solo «Robotica» oppure solo «Programmazione», ma non entrambi. Questa è la differenza simmetrica (xor per insiemi):
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]
Esercizio 2: elenco di tutti gli studenti unici provenienti da più club
Set<String> all = Stream.of(robotics, programming, math)
.flatMap(Set::stream)
.collect(Collectors.toSet());
System.out.println(all); // [Anya, Boris, Vika, Gleb, Dasha, Zhenya]
Esercizio 3: trovare gli studenti che non frequentano alcun club
Supponiamo di avere l’elenco di tutti gli alunni della classe:
Set<String> allStudents = Set.of("Anya", "Boris", "Vika", "Gleb", "Dasha", "Zhenya", "Igor’", "Katya");
Bisogna individuare chi non frequenta alcun club:
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. Note importanti: equals, hashCode ed efficienza
Perché è importante implementare correttamente equals e hashCode?
Tutte le operazioni sugli insiemi (Set) dipendono dalla correttezza dei metodi equals e hashCode. Se memorizzate oggetti di una vostra classe (ad esempio, Student), sovrascrivete sempre questi metodi; altrimenti i confronti non funzioneranno correttamente.
Esempio:
class Student {
String name;
int age;
// Non dimenticare di sovrascrivere equals e hashCode!
}
Se non lo fate, due studenti con lo stesso nome e la stessa età saranno considerati oggetti diversi per il Set.
Perché è meglio usare Set e non List?
- L’operazione contains su Set è veloce (di solito tempo costante).
- In una List la ricerca di un elemento avviene in tempo lineare, il che può essere critico per collezioni grandi.
- Per le operazioni sugli insiemi (union, intersection, difference) Set è molto più efficiente e naturale.
6. Errori tipici nel lavoro con le operazioni sugli insiemi
Errore n. 1: usare List al posto di Set per operazioni sugli insiemi. Se raccogliete gli elementi in List, i duplicati non vengono rimossi e l’operazione contains è lenta. Per union/intersection/difference usate Set.
Errore n. 2: equals/hashCode non implementati per gli oggetti. Se memorizzate in Set oggetti di una vostra classe ma non avete sovrascritto i metodi equals e hashCode, intersezione e differenza si comporteranno in modo “strano” — oggetti che in sostanza sono uguali non verranno considerati equivalenti.
Errore n. 3: modificare la collezione durante lo stream. Se provate a modificare direttamente il Set sorgente (ad esempio, aggiungendo o rimuovendo elementi) all’interno dello stream, otterrete una ConcurrentModificationException. Lavorate sempre con un nuovo insieme.
Errore n. 4: perdita di ordine non evidente. HashSet non garantisce l’ordine degli elementi. Se l’ordine è importante — usate LinkedHashSet o TreeSet.
Errore n. 5: usare Stream.concat per più di due collezioni. Stream.concat concatena solo due stream. Per un numero maggiore usate Stream.of(...) e flatMap.
Errore n. 6: problemi con null. Gli insiemi non amano i valori null, soprattutto se usate Set.of(...) — non ammette null. Per gestire null usate altre implementazioni oppure filtrate i valori in anticipo.
GO TO FULL VERSION