1. Einführung
Beginnen wir mit einem Beispiel aus dem Alltag. Stellen Sie sich vor, Sie organisieren eine Party und erstellen eine Gästeliste. Sie verschicken Einladungen, und später stellt sich heraus, dass dieselbe Person zweimal (oder sogar dreimal – Partys mag man eben!) in der Liste steht. Wenn Sie eine normale Liste (List) verwenden, können solche Duplikate leicht entstehen. Hätten Sie jedoch eine Sammlung, die es von sich aus nicht zulässt, denselben Gast zweimal hinzuzufügen – das Leben wäre einfacher.
Hier kommt die Sammlung Set – eine Menge – ins Spiel.
Set ist eine Sammlung, die nur eindeutige Elemente speichert. Wenn Sie versuchen, ein bereits vorhandenes Element hinzuzufügen, wird es einfach nicht aufgenommen (und niemand ist beleidigt).
Das Interface Set: Grundeigenschaften
In Java ist Set ein Interface, das das Verhalten einer Sammlung ohne Duplikate definiert. Es erbt vom Interface Collection und unterstützt daher Operationen wie Hinzufügen (add), Entfernen (remove), Prüfen auf Vorhandensein (contains) und Iteration.
Schlüsseleigenschaften:
- In einem Set kann es keine zwei identischen Elemente geben.
- Die Elemente können in beliebiger Reihenfolge gespeichert sein – das hängt von der konkreten Implementierung ab.
- Keine Indizes: Man kann nicht wie in einer Liste per Nummer auf ein Element zugreifen.
Deklarationssyntax
Set<String> guests = new HashSet<>();
2. HashSet: schnell, einfach, ohne Ordnung
HashSet ist die populärste Implementierung des Interfaces Set. Es basiert auf einer Hashtabelle (ähnlich wie HashMap, nur ohne das Paar „Key-Value“, sondern lediglich mit eindeutigen Werten). Der Hauptvorteil – Schnelligkeit – liegt bei den Operationen Einfügen, Löschen und Suchen.
Wie funktioniert HashSet?
Stellen Sie sich eine Kiste vor, in die Sie Dinge legen. Damit man schnell erkennen kann, ob schon etwas Ähnliches darin ist, bekommt jeder Gegenstand seine „Nummer“ – den Hashcode. Wenn Sie ein Element in ein HashSet einfügen, wird zunächst dieser Hashcode berechnet. Wenn ein solcher noch nicht vorkam, wird das Element in die Sammlung gelegt. Existiert der Hash bereits, wird zusätzlich die Gleichheit über equals() geprüft. Und nur wenn die Objekte wirklich übereinstimmen, wird das neue Element nicht hinzugefügt.
Das heißt, HashSet kümmert sich automatisch um die Eindeutigkeit: Zwei gleiche Objekte werden darin nicht vorkommen.
Wichtiger Punkt: Wenn Sie mit eigenen Klassen arbeiten und diese in einem HashSet speichern möchten, müssen Sie die Methoden equals() und hashCode() überschreiben. Ohne das kann sich die Sammlung unvorhersehbar verhalten – scheinbar gleiche Objekte werden als verschieden betrachtet.
Grundlegende Methoden von HashSet
Set<String> guests = new HashSet<>();
guests.add("John");
guests.add("Maria");
guests.add("Peter");
guests.add("John"); // Duplikat! Wird nicht hinzugefügt.
System.out.println(guests); // [John, Maria, Peter] – die Reihenfolge kann beliebig sein
guests.remove("Peter"); // Element entfernen
System.out.println(guests.contains("Maria")); // true
System.out.println(guests.size()); // 2
Probieren wir das im Code aus
Angenommen, in unserer Anwendung wollen wir eindeutige Aufgabennamen speichern, damit es nicht zwei Aufgaben mit demselben Titel gibt:
import java.util.HashSet;
import java.util.Set;
public class UniqueTasksDemo {
public static void main(String[] args) {
Set<String> tasks = new HashSet<>();
tasks.add("Java-Hausaufgaben machen");
tasks.add("Die Katze streicheln");
tasks.add("Java-Hausaufgaben machen"); // Duplikat!
System.out.println("Aufgabenliste:");
for (String task : tasks) {
System.out.println("- " + task);
}
// In der Liste stehen nur zwei Aufgaben, das Duplikat wird nicht hinzugefügt
}
}
3. TreeSet: Ordnung ist wichtig!
Manchmal brauchen wir nicht nur Eindeutigkeit, sondern auch einen sortierten Satz von Elementen. Zum Beispiel möchten wir die Namen der Gäste alphabetisch sehen und nicht in zufälliger Reihenfolge. Dafür gibt es TreeSet.
TreeSet ist eine Implementierung des Interfaces Set, die Elemente in aufsteigender Reihenfolge hält. Es basiert auf der Struktur „Rot-Schwarz-Baum“.
Beispiel für die Verwendung von 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("Wally");
guests.add("Alex");
guests.add("Kate");
guests.add("Alex"); // Duplikat!
System.out.println("Gäste (alphabetisch):");
for (String guest : guests) {
System.out.println("- " + guest);
}
// Ausgabe:
// - Alex
// - Wally
// - Kate
}
}
Beachten Sie: Wenn Sie ein Duplikat hinzufügen, erscheint es nicht in der Menge. So, wie es sein soll!
Wann verwendet man TreeSet?
- Wenn eine sortierte Menge eindeutiger Elemente benötigt wird.
- Wenn eine schnelle Suche wichtig ist, die Einfügegeschwindigkeit aber nicht kritisch ist (arbeitet etwas langsamer als HashSet).
- Wenn die Elemente Ihre eigenen Klassen sind, müssen sie „vergleichbar“ sein (das Interface Comparable implementieren) oder Sie stellen einen eigenen Comparator bereit.
Nützliche Feinheiten
HashSet vs. TreeSet: Was soll man wählen?
| Kriterium | HashSet | TreeSet |
|---|---|---|
| Speicherreihenfolge | Nicht garantiert | Aufsteigend sortiert |
| Geschwindigkeit der Operationen | Schneller (O(1)) | Langsamer (O(log n)) |
| Anforderungen an den Typ | Beliebiger Typ (ausreichend: equals()/hashCode()) | Comparable oder Comparator |
| Typische Szenarien | Wenn schneller Zugriff auf eindeutige Elemente benötigt wird | Wenn eine geordnete Ausgabe/Iteration wichtig ist |
Besonderheiten im Umgang mit Set
- Keine Indizes. Anders als bei List hat Set keine Methode get(int index). Wenn Sie Indexzugriff brauchen – verwenden Sie List.
- Keine Duplikate. Versuchen Sie, ein bereits vorhandenes Element hinzuzufügen, wird es nicht hinzugefügt. Die Methode add gibt false zurück.
- Keine Ordnung garantiert (außer bei TreeSet). In HashSet kann die Reihenfolge der Elemente bei jedem Lauf unterschiedlich sein. Wenn die Einfügereihenfolge beibehalten werden soll, verwenden Sie LinkedHashSet.
- Null-Werte.
- HashSet erlaubt genau ein null-Element.
- TreeSet erlaubt null ohne speziellen Comparator nicht; andernfalls gibt es eine NullPointerException.
5. Typische Aufgaben für Set
Duplikate aus einer Liste entfernen
Angenommen, wir haben eine Liste von Studierenden, in der einige doppelt vorkommen. Wir wollen nur eindeutige Namen behalten:
import java.util.*;
public class RemoveDuplicatesDemo {
public static void main(String[] args) {
List<String> students = Arrays.asList("Anna", "Igor", "Anna", "Maria", "Igor", "Paul");
Set<String> uniqueStudents = new HashSet<>(students);
System.out.println("Eindeutige Studierende: " + uniqueStudents);
// Die Reihenfolge ist nicht garantiert!
}
}
Wenn ein sortiertes Ergebnis benötigt wird – verwenden Sie TreeSet:
Set<String> sortedUniqueStudents = new TreeSet<>(students);
System.out.println("Eindeutige Studierende (alphabetisch): " + sortedUniqueStudents);
Einzigartigkeit prüfen (z. B. Benutzerlogin)
Set<String> usedLogins = new HashSet<>();
usedLogins.add("student1");
usedLogins.add("java_lover");
String newLogin = "student1";
if (usedLogins.contains(newLogin)) {
System.out.println("Dieser Login ist bereits vergeben!");
} else {
System.out.println("Login ist frei!");
}
Über die Elemente einer Menge iterieren
Die Iteration erfolgt mit einer for-each-Schleife:
for (String name : uniqueStudents) {
System.out.println(name);
}
6. Häufige Fehler im Umgang mit Set
Fehler Nr. 1: Einen bestimmten Ordnungszustand in HashSet erwarten. Viele Einsteiger wundern sich, warum die Elemente der Menge in einer „merkwürdigen“ Reihenfolge ausgegeben werden. Das ist normal – HashSet garantiert keine Ordnung. Wenn die Einfügereihenfolge benötigt wird, verwenden Sie LinkedHashSet; wenn eine Sortierung benötigt wird – TreeSet.
Fehler Nr. 2: Versuch, per Index auf ein Element zuzugreifen. Manchmal schreibt man etwas wie set.get(0). Das geht nicht: Set unterstützt keine Indizierung. Wird Indexzugriff benötigt – nehmen Sie List.
Fehler Nr. 3: Veränderliche Objekte speichern. Wenn Sie Objekte speichern, die Felder ändern können, die in equals()/hashCode() einfließen, kann ein Element nach Änderungen dieser Felder für die Menge „verloren gehen“. Machen Sie Elemente unveränderlich oder ändern Sie die identifizierenden Felder nicht.
Fehler Nr. 4: Erwarten, dass Duplikate hinzugefügt werden. Dasselbe Element mehrfach hinzuzufügen, erhöht die Größe der Menge nicht – Duplikate werden ignoriert, die Methode add gibt false zurück.
Fehler Nr. 5: Verwendung primitiver Typen. Eine Deklaration wie Set<int> lässt sich nicht kompilieren. Verwenden Sie Wrapper-Klassen: Set<Integer>, Set<Double> usw.
GO TO FULL VERSION