Set
Kurz: Ein Collections-Interface für Sammlungen ohne Duplikate — jedes Element kann höchstens einmal enthalten sein.
Genauer: Fügt man ein bereits vorhandenes Element erneut hinzu, ändert sich am Set nichts (kein Fehler, aber auch kein zweiter Eintrag). Die konkreten Implementierungen HashSet (keine garantierte Reihenfolge, sehr schnell) und TreeSet (sortiert, etwas langsamer) unterscheiden sich vor allem darin, wie sie Elemente intern organisieren.
Im Detail
Set<String> namen = new HashSet<>();
namen.add("Anna");
namen.add("Bob");
namen.add("Anna"); // wird ignoriert, kein Duplikat
System.out.println(namen.size()); // 2
if (namen.contains("Anna")) {
System.out.println("Anna ist schon dabei");
}
namen.remove("Bob");Ob ein Element als “gleich” zählt, entscheidet nicht == (Objektidentität), sondern die equals()/hashCode()-Implementierung der gespeicherten Klasse. Bei eigenen Klassen muss man deshalb beide Methoden konsistent überschreiben — sonst landen zwei inhaltlich identische Objekte trotzdem doppelt im Set, weil HashSet sie über unterschiedliche hashCode()-Werte als verschieden einordnet. Set selbst ist nur ein Interface (Teil von Collections); welche konkrete Implementierung man wählt, hängt vom Bedarf ab:
- HashSet: schnellster Zugriff (O(1) im Schnitt), keine garantierte Reihenfolge.
- TreeSet: immer sortiert, dafür langsamer (O(log n)).
LinkedHashSet: behält die Einfügereihenfolge bei, ein Kompromiss zwischen den beiden.
Typischer Einsatzzweck: Duplikate aus einer Liste entfernen, indem man sie kurzerhand in ein Set überführt (new HashSet<>(liste)), oder schnell prüfen, ob ein Wert schon vorgekommen ist, ohne die ganze Collection linear zu durchsuchen wie bei einer List.
Mengenoperationen — Vereinigung, Schnittmenge, Differenz
Set bildet die mathematischen Mengenoperationen direkt über Collection-Methoden ab, die die aufrufende Menge selbst verändern:
Set<Integer> a = new HashSet<>(Set.of(1, 2, 3, 4));
Set<Integer> b = new HashSet<>(Set.of(3, 4, 5, 6));
Set<Integer> vereinigung = new HashSet<>(a);
vereinigung.addAll(b); // {1, 2, 3, 4, 5, 6}
Set<Integer> schnittmenge = new HashSet<>(a);
schnittmenge.retainAll(b); // nur Elemente in BEIDEN: {3, 4}
Set<Integer> differenz = new HashSet<>(a);
differenz.removeAll(b); // nur in a, nicht in b: {1, 2}Da addAll()/retainAll()/removeAll() die aufrufende Menge direkt verändern, wird hier bewusst zuerst eine Kopie (new HashSet<>(a)) erzeugt, um das Original unangetastet zu lassen.
Warum equals()/hashCode() bei eigenen Klassen entscheidend sind
Ohne überschriebenes equals()/hashCode() vergleicht Java Objekte standardmäßig nach Referenz (Speicheradresse) — zwei inhaltlich identische, aber separat erzeugte Objekte gelten dann als “verschieden” und landen beide im Set:
class Punkt {
int x, y;
Punkt(int x, int y) { this.x = x; this.y = y; }
// OHNE equals()/hashCode(): zwei Punkt(1,1)-Objekte gelten als verschieden!
}
Set<Punkt> punkte = new HashSet<>();
punkte.add(new Punkt(1, 1));
punkte.add(new Punkt(1, 1)); // wird NICHT als Duplikat erkannt ohne equals()/hashCode()
System.out.println(punkte.size()); // 2, nicht 1!Siehe auch: HashSet, TreeSet, Collections