.

Das Set Interface repräsentiert eine Collection Art die bei der Java Programmierung häufig zum Einsatz kommt. Ansich lässt ein Set keine doppelten Elemente zu. Was bedeutet alle Elemente in einem Set sind einmalig.

Übersicht – Set Interface

Die folgenden Eigenschaften unterscheiden ein Set von anderen Collections im Java Collections Framework:

  • Doppelte Elemente sind nicht zulässig.
  • Es gibt in der Regel keine Ordnung innerhalb des Set. Das bedeutet die Elemente in einem Set haben keine definierte Reihenfolge wenn man über das Set iteriert.

Einsatz eines Set

Aufgrund seiner Eigenschaften kann man bestimmen wann der Einsatz eines Set sinnvoll ist.

  • Es werden Elemente ohne Duplizierung oder eindeutige Elemente benötigt.
  • Die Reihenfolge der Elemente spielt keine Rolle.

Zum Beispiel wird ein Set verwendet, um eindeutige Ganzzahlen zu speichern. Diese könne in einem Kartenspiel die Karten repräsentieren, die zufällig zu speichern sind. Wie bei einem Kartenstapel von dem die Spieler ziehen.

Set Interface Implementierungen

Das Java Collections Framework kennt drei Hauptimplementierungen des Set Interface: HashSet<E>, LinkedHashSet<E> und TreeSet<E>. Hier ein Ausschnitt aus dem Klassendiagramm:

Java Colletion Set Interface Klassendiagram

Eine Auflistung aller bekannten Implementierungen findet sich in der Java Set Interface Dokumentation.

Werfen wir einen Blick auf die Eigenschaften der drei wichtigsten Implementierungen:

  • HashSet<E> – Ist die leistungsstärkste Implementierung und vermutlich die am häufigsten genutzte Set Implementierung. Es repräsentiert die Kernmerkmale von Mengen: keine Vervielfältigung und ungeordnet.
  • LinkedHashSet<E> – Entgegen eines üblichen Set haben die Elemente hier eine Ordnung, basierend auf der eingefügten Reihenfolge. Somit bietet sich das LinkedHashSet an, wenn eindeutige Elemente in einer definierten Reihenfolge gespeichert werden sollen.
  • TreeSet<E> Auch hier haben die Elemente im Set eine Ordnung. Diese basiert aber auf den Werten der Elemente, bzw. auf ihrer natürlichen Reihenfolge. Alternativ kann ein Comparator genutzt werden, der zum Zeitpunkt der Erstellung des TreeSet bereitgestellt werden muss.

Somit ergeben sich entsprechende Einsatzszenarien für die verschiedenen Implementierungen des Set. Daher sollten Sie neben der Einzigartigkeit der Elemente, die ein Set garantiert, die Verwendung von HashSet in Betracht ziehen, wenn die Bestellung keine Rolle spielt. Verwenden von LinkedHashSet, wenn Sie Elemente nach ihrer eingefügten Reihenfolge sortieren möchten. Verwenden von TreeSet, wenn Sie Elemente nach ihren Werten sortieren möchten. Die Codebeispiele in diesem Lernprogramm verwenden hauptsächlich die HashSet Implementierung.

  • Reihenfolge spielt keine Rolle -> HashSet<E>
  • Einfügereihnefolge relevant -> LinkedHashSet<E>
  • Natürliche- oder spezielle Reihenfolge erwünscht -> TreeSet<E>

Ein neues Set erstellen

Es sollten immer generische Sets erstellt werden bei dem der Typ der Objekte die im Set gespeichert werden sollen definiert ist.

Set<Integer> cards = new HashSet<>();
Set<String> names = new LinkedHashSet<>(); 

Natürlich lassen sich Sets auch aus bestehenden Colletions erstellen. Dies kann man auch als Trick nutzen um doppelte Einträge zu entfernen.

List<Integer> listNums = Arrays.asList(4, 7, 4, 8, 2, 7, 3); System.out.println(listNums);
Set<Integer> uniqueNums = new HashSet<>(listNumbs);
System.out.println(uniqueNums);

Ausgabe:

[4, 7, 4, 8, 2, 7, 3]
[7, 8, 3, 4, 2]

Wie man sieht wurden die doppelten Elemente der List nicht mit ins Set übernommen. Da ein HashSet verwendet wurde ist aber auch die Reihenfolge nicht mehr definiert.

Seit Java 8 kann auf eine Collection ein Stream angewendet werden um zum Beispiel den Inhalt zu filtern und diesen dann in einem Set ab zu legen.

Set<Interger> uniqueEvenNums = listNums.stream().filter(n -> n % 2 == 0).collet(Collectors.toSet));
System.out.println(uniqueEvenNums);

Ausgabe:

[4, 2, 8]

Die übliche Kapazität eines HashSet und LinkedHashSet ist 16. Wenn schon beim erstellen des Set klar ist das mehr als 16 Elemente in dem Set hinterlegt werden sollen, ist es von vorteil die gewünscht Kapazität beim erstellen an zu geben.

Set<Integer> articles = new HashSet<>(10000);

Hier wird ein HashSet erstellt um dort 10000 Artikel zu speichern. So muss beim Hinzufügen der Artikel nicht immer wieder die Kapazität erhöht werden. Was einen Performancegewinn bedeutet.

Grundlegende Methoden des Set Interface

Elemente hinzufügen

Die add(Object) Methode gibt ‚true‘ zurück wenn das Set das übergebene Element noch nicht enthält, ‚false‘ falls das Element bereits im Set enthalten ist.

Set<String> students = new HashSet<>();
students.add("Jakob");
students.add("Martin"); 
if (students.add("Thomas")) {
  System.out.println("Thomas is added to the set");
} 
if (!students.add("Jakob")) {
  System.out.println("Jakob is already in the set");
}

Ausgabe:

Thomas is added to the set
Jakob is already in the set

Ein Set kann auch ein ’null‘ Element enthalten.

students.add(null);

Element entfernen

Die remove(Object) Methode entfernt das angegebene Element aus dem Set, sofern es im Set enthalten ist. Die Methode gibt ‚true‘ zurück zur bestätigung des löschen, anderenfalls ‚false‘.

if (names.remove("Martin")) {
  System.out.println("Martin is removed");
}

Wichtig ist das die Objekte des Set die equals() und hashCode() Methode geeignet implementieren. Die ist Voraussetzung damit das Set Elemente finden und dann löschen kann.

Alle elemente eines Set können mit der clear() Methode auf einen Schlag gelöscht werden.

students.clear();

Testen ob ein Set leer ist

Die isEmpty() Methode gibt ‚true‘ zurück wenn ein Set keine Elemente enthält, anderenfalls gibt sie ‚false‘ zurück.

if (students.isEmpty()) {
  System.out.println("The set is empty");
} else {
  System.out.println("The set is not empty");
}

Anzahl der Elemente in einem Set

Die size() Methode gibt die Anzahl der Elemente zurück die im Set enthalten sind.

Set<String> students = new HashSet<>();
students.add("Jakob");
students.add("Martin"); 
students.add("Thomas");
System.out.printf("The set contains %d students", students.size());

Ausgabe:

The set contains 3 students

Zu beachten ist, dass das Set Interface keine Methode zum Abrufen eines bestimmten Elements bereitstellt, da diese ungeordnet sind. Nur die TreeSet Implementierung erlaubt das abrufen des ersten und letzte Element.

Über ein Set iterieren

Mit dem Iterator

Set<String> students = new HashSet<>();
students.add("Jakob");
students.add("Martin"); 
students.add("Thomas");
iterator<String> iterator = students.iterator();
while (iterator.hasNext()) {
  String name = iterator.next();
  System.out.println(name + " ");
}

Ausgabe:

Martin Thomas Jakob

Nutzung der erweiterten for Schleife

for (String student : students) {
  System.out.println(student);
}

Seit Java 8 ist es auch möglich die Methode forEach() zu nutzen. Zusammen mit eine Methodenreferenz.

students.forEach()(System.out::println);

Ausgabe:

Thomas
Jakob
Martin

Element suchen

Die contains(Object) Methode gibt ‚true‘ zurück wenn das Set das übergebene Element enthält, anderenfalls ‚false‘.

Set<String> students = new HashSet<>();
students.add("Jakob");
students.add("Martin"); 
students.add("Thomas");
if (names.contains("Thomas")) {
  System.out.println("Found Thomas");}

Auch hier ist es wichtig das Objekte eines selbst definierten Typ die Methoden equals() und hashCode() passend implementieren, damit das Set die Elemente finden kann.

Ausführen von Bulk Operations auf zwei Sets

Zwischen zwei Sets können sogenante Bluk Operations ausgeführt werden die auf grundlegenden mathematischen Operationen auf Mengen basieren. Dies sind: Teilmenge, Vereinigung, Schnittmenge und Mengendifferenz. Im weiteren verlauf gegen wir davon aus das wir zwei Sets haben, s1 und s2.

Set<Integer> s1 = new HashSet<>(Arrays.asList(20, 56, 89, 8, 5, 31)); 
Set<Integer> s2 = new HashSet<>(Arrays.asList(89, 8, 3));

Teilmengen?

s1.containsAll(s2) – gibt ‚true‘ zurück wenn s2 eine Teilmenge von s1 ist. Also alle Elemente von s2 ebenfalls in s1 enthalten sind.

if (!s1.containsAll(s2)) {
  System.out.println("s2 isn`t a subset of s1");
}

Ausgabe:

s2 isn`t a subset of s1

Vereinigungs Operation

s1.addAll(s2) – verwandelt s1 in s1 vereinigt mit s2. Aus der Vereinigung resultiert ein Set das alle Elmente beider Sets enthält.

System.out.println(s1); 
System.out.println(s2);
s1.addAll(s2);
System.out.println(s1);

Ausgabe:

[20, 56, 89, 8, 5, 31]
[89, 8, 3]
[3, 20, 56, 89, 8, 5, 31]

Schmitmengen Operation

s1.retaintAll(s2) – verwandelt s1 in ein Set das die Schnittmenge von s1 und s2 enthält. Es bleiben somit nur Elemente enthalten die in beidenSets vorkommen.

System.out.println(s1); 
System.out.println(s2);
s1.retaintAll(s2);
System.out.println(s1);

Ausgabe:

[20, 56, 89, 8, 5, 31]
[89, 8, 3]
[8, 89]

Mengendifferenz Operation

s1.removeAll(s2) – verwandelt s1 in eine (asymmetrische) Mengendifferenz von s1 und s2. Das bedeutet s1 minus s2 oder erhalten bleiben alle Elemente von s1 die nicht in s2 enthalten sind.

System.out.println(s1); 
System.out.println(s2);
s1.removeAll(s2);
System.out.println(s1);

Ausgabe:

[20, 56, 89, 8, 5, 31]
[89, 8, 3]
[20, 56, 5, 31]

Parallele Verarbeitung von Sets

Standard mäßig sind die Implementierungen HashSet, LinkedHashSet und TreeSet nicht threadsicher. Wenn diese also im parallelen Verarbeitungs-Kontext verwendet werden sollen, muss extern mittels der statischen Methode Collections.synchronizedSet() synchronisiert werden. Diese gibt eine threadsicher Liste zurück, die die angegebene Liste umschließt.

Set<Integer> numbers = Collections.synchronizedSet(new HashSet<Integer>());

Um über eine solche threadsichere Liste zu iterieren muss diese manuell synchronisiert werden.

synchronized (numbers) {
  Iterator<Integer> iterator = numbers.iterator(); 
  while (iterator.hasNext()) {
    Integer number = iterator.next();
    System.out.println(number);
  }
}

Weitere Operationen auf Sets

Die Colletions utility Klasse bietet witere Methoden an die auf Set basierten Colletions angewendet werden können.

  • checkedSet() – Gibt eine dynamisch typsichere Ansicht des übergebenen Set zurück.
  • checkedSortedSet() – Gibt eine dynamisch typsichere Ansicht des übergebenen sortierten Set zurück.
  • emptySet() – Gibt die leere Menge zurück (unveränderlich).
  • singleton() – Gibt eine unveränderliche Menge zurück, die nur das übergebene Objekt enthält.
  • unmodifiableSet() – Gibt eine nicht modifizierbare Ansicht des übergenenen Set zurück.
  • unmodifiableSortedSet() – Gibt eine nicht modifizierbare Ansicht des übergenenen sortierten Set zurück.

Basierend auf dem Artikel Java Set Collection Tutorial and Examples von Nam Ha Minh.


0 Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.