Collections-Framework: List, Set und wichtige Basiskonzepte
1. Zusätzliche Informationsquellen
Als Einstieg zur diesbezüglichen Java-API-Dokumentation kann folgender Link dienen:
Collections Framework Overview (Java SE 17 & JDK 17) - docs.oracle.com … 2022-11-01
Weiteres findet sich z.B. unter …
2. Überblick
2.1. Collections und Java Collections-Framework
Ein grundlegendes Konzept der IT ist das Organisieren von Sammlungen gleichartiger Daten. Nahezu jede Programmiersprache stellt dafür Arrays direkt im Sprachkern bereit. Je nach Sprache ist damit grundlegende – eher auf Effizienz optimierte – Funktionalität verfügbar oder es werden auch weitergehende Möglichkeiten bereitgestellt.
Java hat (derzeit) den ersten Weg im Angebot und lagert weitergehende Funktionalitäten in ein separates Framework aus – das Collections-Framework.
Dieses besteht als konzeptuelle Grundlage aus einem Satz von Interfaces, beginnend mit dem Basis-Interface Collection
, von dem die Interfaces List
und Set
erben.
Parallel dazu gibt es das Basis-Interface Map
, das die Grundlage zur Verwaltung von Schlüssel-Wert-Paaren bildet und eng mit Collections verbunden ist.
Um wie in Arrays effizient Typsicherheit bezüglich der Elemente sicherzustellen, gibt es ein weiteres, grundlegendes Sprachkonzept, das schon zuvor in Sprachen wie C, C++ verfügbar war: Generics.
3. Ein kurzer Blick auf Arrays
In Java-Arrays ist exakt geklärt, welche Objekt-Arten im Array aufgenommen werden können:
Mit SomeClass[] oneArray;
oder SomeInterface[] otherArray;
liegt der Element-Typ fest.
WICHTIG und nicht naheliegend: auch Arrays von Interfaces sind möglich, die Elemente müssen alle das Interface implementieren!
Der markanteste Nachteil von Arrays ist die fehlende Möglichkeit, die Kapazität dynamisch zu verändern. Als einfacher, aber bei großer Element-Anzahl sehr ineffizienter Umweg bleibt das Austauschen des Arrays durch ein anderes mit passenderer Größe. Dabei muss einerseits darauf geachtet werden, dass alle Referenzen korrigiert werden (eine in manchen Konstellation äußerst schwierige Aufgabe), andererseits müssen alle Elemente des alten Arrays in das neue kopiert werden (bei z.B. Millionen Elementen sehr aufwändig).
Hier ein Beispiel zu einem Array mit Interface als Typ:
SomeIface.java
:public interface SomeIface {
void sampleMethod1();
}
SomeClass1.java
:public class SomeClass1 implements SomeIface {
private String name;
public SomeClass1(String name) {
this.name = name;
}
@Override
public void sampleMethod1() {
System.out.println("Now running sampleMethod1() for Obj. with name: " + name);
}
}
public class ArrayDemo1 {
public static void main(String[] args) {
SomeIface[] arrOfSomeIface = new SomeIface[2];
arrOfSomeIface[0] = new SomeClass1("Evi");
arrOfSomeIface[1] = new SomeClass1("Karli");
for (SomeIface item : arrOfSomeIface) {
item.sampleMethod1();
}
}
}
4. Grundlegende Alternativen zur Verwaltung von Kollektionen
4.1. Verkettete Listen
Verkettete Listen haben als grundlegendes Element Objekte einer "Node"-Klasse. Diese haben ein Attribut für ein "Nutzlast"-Objekt (Payload) und als Verkettungs-Attribut (z.B. next
genannt) eine Referenz auf ein mögliches Nachfolger-Objekt.
Damit kann man sich von einem Node-Objekt zum nächsten greifen und so in einer Schleife bis zum letzten Node-Objekt gelangen (dieses erkennt man daran, dass next
eine null
-Referenz ist).
Sobald man eine Referenz auf das erste Node-Objekt hat, kann man die gesamte Liste durchlaufen – allerdings nur sequenziell in einer Richtung! Bei sehr langen Listen ist das Erreichen weiter hinten liegender Elemente recht langsam. In Arrays hingegen kann durch den Index und den Speicherplatz-Bedarf für ein Element die Adresse der passenden Zelle berechnet und direkt angesprungen werden.
Sie haben einen wichtigen Vorteil: Einfügen eines weiteren Elements erfordert nur das Erzeugen eines neuen Node-Objekts und Umsetzen der next
-Referenzen. Besonders effizient ist das Einfügen am Anfang, da hier keinerlei sequenzielle Navigation zum Ziel-Node nötig ist.
Wenn Navigation in beide Richtungen erforderlich ist, erfolgt eine Verkettung in beide Richtungen, man hat also ein weiteres Attribut im Node: z.B. prev
(für previous/voriges) und neben der Referenz auf das erste Element auch eine auf das letzte.
4.2. Bäume
Viel mehr Potential hat das Konzept von Bäumen - hier können äußerst vielgestaltige und leistungsfähige Datenstrukturen gebindet werden. Bäume sind monohierarchische Strukturen, jedes Element hat genau ein "Eltern"-Element, nur eines hat keines – das Wurzel-Element (root).
Damit können unter anderem Erweiterungen zu verketteten Listen mit Entschärfung des mühsamen sequenziellen Durchlaufens konzipiert werden.
in Arbeit ...
4.3. Verwandtes
https://de.wikipedia.org/wiki/Klassifikation [Klassifikation – Wikipedia] … 2022-11-01
5. Wichtige Basiskonzepte
5.1. Einführung Generics
Generics beheben das Typsicherheits-Problem, das sonst hinsichtlich der Listen-Elemente bestehen würde (oder eine wesentlich mühsamere Sicherstellung erfordern würde), indem sie als weiteres Sprach-Konstrukt Typ-Parmeter (in spitzen Klammern) bereitstellen, die u.a. für Collections, aber auch für viele andere Bereiche Typ-Flexibilität und Typsicherheit ermöglichen.
Generics erlauben höchst komplexe Typ-Konstrukte, wir beschränken uns hier auf den in Collections und Maps häufig auftretenden trivialen Einsatz.
Das "Strickmuster" ist z.B.:
List<Person> personen = new ArrayList<Person>();
// oder (seit Java 8 möglich) "Diamond-Operator" "<>" :
List<Person> personen = new ArrayList<>(); // Typ-Parameter für ArrayList vom Compiler klärbar
Der "Diamond-Operator" "<>" wirkt hier äußerst unspektakulär. Sein Komfort-Potential wird erst erahnbar, wenn man die oben erwähnten komplexen Typ-Konstrukte <…> vor Augen hat. Generics können verschachtelt werden, mehrere Generics-Parameter enthalten, es gibt "Wildcards" und "Subtyping" – es gibt Generics-Klammern mit mehreren Zeilen Länge!
Vergleichsweise simples Beispiel: In der Utility-Klasse java.util.Collections
gibt es Methode
static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)
.
Für weitergehende Information zu Generics siehe z.B.:
Wildcards and Subtyping (The Java™ Tutorials > Learning the Java Language > Generics (Updated))
und alle weiteren Seiten im links angeordneten Inhaltsverzeichnis. Wurzel-Eintrag:
Lesson: Generics (Updated) (The Java™ Tutorials > Learning the Java Language) … 2021-01-09
5.2. equals(..) und hashCode()
Diese dienen als Basis der Objekt-Identifikation (equals(..)
) bzw. zur effizienten Auffindung in Objekt-Sammlungen (hashCode()
) und sind schon innerhalb der Klasse Object
erstmalig definiert.
Die Identifikation soll häufig die Objekt-Idee betreffen, nicht die "physische" Repräsentation im Computer. Beispielsweise geht es bei String-Identifikation um die Zeichenabfolge und nicht um die Repräsentation im Hauptspeicher.
Auch die Fähigkeit von Java, verteilte Applikationen umzusetzen, wo Programmteile über Netzwerk kooperieren oder die Möglichkeit Datenstruktur-Zustände mit ihren Zusammenhängen abzuspeichern und später wieder zu reaktivieren (Serialisierung/Deserialisierung) führen zu ähnlichen Problemstellungen.
Der Vergleich zweier Objekt-Referenzen (per Literal, Variable, Rückgabewert, Ausdruck) mit dem Operator ==
vergleicht die Inhalte auf Übereinstimmung. Die Inhalte von Referenzen zeigen bei Übereinstimmung af das selbe Objekt im Hauptspeicher – true
bedeutet also Identität – die extremste Form der Gleichheit.
Dies ist aber extrem oft gar nicht nötig – zwei Texte sind als gleich zu betrachten, wenn sie die gleiche Zeichenfolge enthalten, zwei LocalDate-Objekte sind als gleich zu betrachten, wenn sie den selben Punkt auf einer Zeit-Achse bedeuten. In beiden Fällen ist die Objekt-Identität überhaupt nicht von Bedeutung.
Auch bei zwei separat erzeugten Personen-Objekten ist es z.B. oft ausreichend, wenn sie die selbe Sozialversicherungsnummer haben, um sie in einer Verwaltungsapplikation als "die selbe Person meinend" zu identifizieren.
Auch die Kombination aus (Geburts-)Familienname, Vorname, Geburtsdatum und Ort ist in der öffentlichen Verwaltung zur Identifikation geeignet (Ein einziges Standesamt darf am selben Tag keine zwei Neugeborenen Franz Meier erlauben, um diesen beiden eine lebenslange Verwechslungsmöglichkeit bei Verträgen aller Art zu ersparen).
In Verwaltungssystemen gibt es viele gängige Merkmale oder Merkmals-Kombinationen, die sich zur Identifikation eignen (Primary Keys in relationalen DBs).
5.3. Methode equals(…)
Im einfachsten Fall ist ein Objekt eine logisch kompakte, sehr eindeutig fassbare Einheit mit einem Satz von Attributen und eigener Identität im Hauptspeicher.
In den oben erwähnten Szenarien muss aber mehr Flexibilität eingeführt werden. Daher wird bei Objekten die bzw. Gleichheit / Gleichwertigkeit / Ersetzbarkeit / Übereinstimmung mit einer eigenen Methode überprüft:
…; if (oneObject.equals(otherObject) { … }
Die Methode boolean equals(Object obj)
kann überschrieben werden und erlaubt die exakte Umsetzung benötigter Gleichheits-Konzepte.
Wenn Objekte z.B. aus Daten einer Relationalen Datenbank erzeugt werden, ist als Identitätsmerkmal der Primary Key perfekt geeignet – Übereinstimmung des Primary Key muss dann, um ein konsistentes System sicherzustellen, in vielen Fällen als einziges Kriterium verwendet werden.
WICHTIG: Allerdings ist dieser oft erst nach INSERT in der DB verfügbar, davor kann die equals(..)
-Methode nicht verwendet werden – wenn wirklich nötig, muss bis dahin mit Vergleich eines identifizierenden Satzes an Einzel-Attributen gearbeitet werden!
Etwaige Differenzen in anderen Attributen sind dann für die Identifikation irrelevant – das ist z.B. praktisch, wenn ein neues Objekt zur Suche mit der Collection-Methode personen.contains(new Person("nn", "vn", wantedSvNr))
erstellt wird (nur die Sozialversicherungsnummer wantedSvNr
ist zur Identifikation releveant).
Bei Personen ist z.B. die Sozialversicherungsnummer o.ä. als Identitäts-Attribut sehr gut geeignet (falls sicher verfügbar), bei der Benutzerverwaltung einer Cloud-Lösung ist die E-Mail-Adresse ein Kandidat als Identitäts-Attribut.
In vielen Fällen ist die Kombination einiger Attribute eindeutig und damit gut geeignet – z.B. für Personen ist die Kombination aus Nachname, Vorname, Geburtsdatum und Geburtsort eindeutig (Standesämter dürfen innerhalb eines Ortes fürs gleiche Geburtdatum nicht 2 "Franz Meier" erlauben, um für Betroffene lebenslange Identifizierungsprobleme zu vermeiden).
Die Logik hinter all diesen Fällen ist recht analog und daher lässt sich die Methode equals(…)
nach Auswahl des identitätsbestimmenden Satzes an Attributen automatisch erzeugen.
5.4. Methode hashCode()
Die Frage der Gleichheit stellt sich extrem häufig in Zusammenhang mit Kollektionen von Objekten. Für die effiziente Verwaltung solcher Kollektionen ist das Konzept der Hash-Codes äußerst wichtig - es ermöglicht sehr performante Auffindung bestimmter Objekte in großen Kollektionen.
Sehr salopp ausgedrückt ist eine HashCode eine Ganze Zahl (in Java Typ int
) und wird mit einfachen (schnellen) Berechnungen aus Attributen des jeweiligen Objekts gebildet, sodass sie auch für große Mengen sehr rasch erzeugt werden können und dennoch statistisch relativ wenige Doubletten haben (z.B. eines Hash-Sets aus einer großen ArrayList).
In Java gibt es dafür eine "Override-able" Standardimplementation in Klasse java.lang.Object
mit Methodensignatur: int hashCode()
.
Details dazu […klicken zum Auf/Zuklappen…]
Damit kann man in einer Kollektion die Objekte (zusammen mit ihrem HashCode) nach Hashcode sortiert ablegen – diejenigen mit selbem Hash-Code liegen daher "beisammen" (in sortierten Kollektionen kann die Position eines Wertes extrem effizient erfolgen – Bisektionsverfahren erfordern bei Verdopplung der Element-Zahl nur einen einzigen Schritt mehr, Suchzeit steigt also drastisch weniger als linear mit der Anzahl!).
Bei Suche nach einem Objekt in der Kollektion wird dessen Hash-Code gebildet und man bekommt als "Antwort" alle (hoffentlich wenige) Objekte mit diesem Hash-Code. Nun muss man sequentiell durchsehen (mit equals()
), ob das gesuchte Objekt dabei ist.
Man kann schon "riechen", dass Hash-Codes und die equals(…)
-Methode eng verknüpft sind – wenn sie inkonsistent definiert sind, funktioniert das Objekt-Auffinden in mit Hash-Codes arbeitenden Kollektions-Typen nicht (diese sind aber für viele Zwecke unersetzlich)!
Hash-Codes haben auch die angenehme Eigenschaft, dass sie eine reine Objekt-Eigenschaft sind, also von nichts anderem als den Objekt-Attributen abhängig sind (nur für Effizienz-Optimierung sollte man in bestimmten Fällen über Attributwert-Häufigkeiten u.ä. des Gesamtbestandes nachdenken). Beim Erzeugen sind daher nur Attribute des jeweiligen Objekts erforderlich.
Hashcodes sind jedenfalls ebenfalls (wie auch equals(..)
) nach Auswahl der heranzuziehenden Attribute automatisch mit guter Qualität erzeugbar. Daher gibt es Werkzeuge (z.B. direkt in "besseren" IDEs), die die beiden Methoden "in einem Aufwaschen" zueinander konsistent generieren.
Die Regel zur Konsistenz ist, dass ALLE Objektpaare o1
, o2
, die bei o1.equals(o2)
true
liefern, auch den selben HashCode haben (NICHT nötigerweise umgekehrt).
Die Regel für effizientes Auffinden ist, dass zu jedem vorhandenen HashCode-Wert nicht zu viele Objekte gehören. Extremfälle wären:
-
Jeder HashCode hat maximal 1 Objekt - dann ist überhaupt kein Vergleich mit
equals(..)
nötig: man berechnet den HashCode des geünschten ObjektoX
, lokalisiert diesen Wert in der geordneten Liste der HashCodes und hat sofort die damit verbundene einzige Objekt-Referenz. -
Alle Objekte der Kollektion haben den selben HashCode, es gibt also nur einen einzigen HashCode für alle Objekte. Dann wird der HashCode des gewünschten Objekts (hier
o
genannt) berechnet, man gelangt an den Anfang der HashCode-Liste und beginnt, jedes KollektionsobjektoX
mitoX.equals(o)
zu prüfen, bis die alle Elemente geprüft sind oder ein Treffer erfolgt ist.
Sehr gute, detaillierte Beschreibung (deutsch): AngelikaLanger.com 2002 - Implementing the hashCode() Method - Angelika Langer Training/Consulting
5.5. Beispielklasse "Person" mit equals(…), hashCode() etc.
-
Es werden zu Demonstrationszwecken zwei Konstante deklariert (siehe oben).
-
Weiters wird eine Enumeration (
enum
= Aufzählung erlaubter Einzelwerte) verwendet.
SourceCode […klicken zum Auf/Zuklappen…]
public class Person {
public static final int MIN_BIRTH_YEAR = 1900; // durch 'final' konstant -->
public static final int MAX_BIRTH_YEAR = 2022; //.. MÜSSEN in Deklaration Wert erhalten
public enum Gender { // Aufzählungstyp (kann in Java viel mehr ...)
FEMALE, MALE, OTHER, UNKNOWN;
}
private String lastname;
private String firstname;
private Gender gender; // der obige enum-Typ
private int birthYear;
private boolean smoker;
private String email;
// ...
@Override // schon in Klasse Object definiert
public int hashCode() {
// in der JDK-Methode Objects.hash(...) wird schon die ganze Arbeit erledigt:
return Objects.hash(this.birthYear, this.firstname, this.gender, this.lastname);
}
@Override // schon in Klasse Object definiert
public boolean equals(Object obj) {
if (this == obj) // identisches Objekt übergeben (sich selbst)
return true;
if (!(obj instanceof Person)) // <-- falls 'obj' keine Person, kann es nicht gleich sein
return false; // |
Person other = (Person) obj; // obiges 'instanceof' sichert die Gültigkeit des type cast
return this.birthYear == other.birthYear && Objects.equals(this.firstname, other.firstname)
&& this.gender == other.gender && Objects.equals(this.lastname, other.lastname);
}
}
Eine ausführlichere Besprechung zu einer erweiterten Fassung dieses Beispiels findet sich unter Demo zu equals(…), hashCode(), compareTo(…), Collections und Weiterem
5.6. Das for-each Statement
Wird auch "enhanced for loop" genannt und dient zu Iteration über Arrays und Collections – generell über alle Klassen, die Interface Iterable
implementieren.
Details siehe Iterable (Java SE 17 & JDK 17) - docs.oracle.com … 2022-11-01
String[] someColors = {"black", "white", "red", "green", "blue", "yellow", "brown"};
System.out.format("List of %d colors:%n", someColors.length);
String separator = "[";
for (String color : someColors) {
System.out.format("%s%s", separator, color);
separator = ", ";
}
System.out.println("]");
die gleiche Aufgabe mit klassischem For-Statement:
String[] someColors = {"black", "white", "red", "green", "blue", "yellow", "brown"};
System.out.format("List of %d colors:%n", someColors.length);
String separator = "[";
for (int i = 0; i < someColors.length; i++) {
String color = someColors[i]; // oder unten gleich someColors[i]
System.out.format("%s%s", separator, color);
separator = ", ";
}
System.out.println("]");
Markant am For-Each-Loop ist, dass eine sehr kompakte Iteration möglich wird, jedoch keine Laufvariable für den Index bereitsteht. Das macht sie aber auch zur Iteration über Collections ohne definierte Ordnung (Sets, siehe unten) fähig.
6. Interface "Collection"
Sammlung/Kollektion ist ein sehr genereller Oberbegriff für das gemeinsame Verwalten von Datenelementen.
Einige Operationen sollte eine Sammlung normalerweise erlauben, z.B. Elementanzahl erfragen (size), Enthalten-Sein von Elementen prüfen (contains), Hinzufügen von Elementen (add), Entfernen von Elementen (remove).
In Java gibt es als Basis von Sammlungen das Interface java.util.Collection
, das einerseits allgemeine Klärungen vornimmt (Elemente müssen Objekte sein, primitive Datentypen nur mittels ihrer Wrapper-Klasse) und anderereseits Methoden für solche oben genannte Operationen in mehreren Varianten bereitstellt.
Details siehe Collection (Java SE 17 & JDK 17) - docs.oracle.com … 2022-11-02
Es gibt einige als optional operstion gekennzeichnete Methoden. In Implementationen, wo eine solche Operation nicht (nutzbar) implementiert ist, wird bei Aufruf eine UnsupportedOperationException
geworfen. Für andere Einschränkungen werden bestimmte andere Exceptions geworfen.
So gibt es Implementationen mit unveränderlicher Größe oder mit Verbot von null
-Werten.
6.1. Einige wichtige Operationen
Die Beispiele beruhen auf der Annahme, dass wir eine Collection von Personen, die fürs Instanzieren natürlich eine konkrete Klasse (z.B. ArrayList, Details siehe unten erfordern) haben:
Collection<Person> persons = new ArrayList<>();
-
Erfragen der aktuellen Element-Anzahl:
int size()
, also z.B.:
int howMany = persons.size();
-
Kontrolle, ob ein als Parameter übergebenes Objekt in Liste enthalten:
boolean contains(E elem)
, also z.B.:
if (persons.contains(new Person(…))) { … }
das Personen-Objekt kann problemlos neu erzeugt werden, wennequals(..)
bei gewollter Gleichheittrue
zurückliefert -
Hinzufügen eines Elements am Ende:
boolean add(E elem)
…E
ist der "Generic Type Parameter", also z.B.:
persons.add(new Person(…));
-
Entfernen eines als Parameter übergebenen Objekts (falls in der Liste):
boolean remove(E item)
, also z.B. für verfügbaresPerson
-Objektp1
:
if (persons.remove(p1)) { … }
alternativ siehe Kommentar zucontains(..)
-
Transfer der Elemente in ein Array:
<T> T[] toArray(T[] a)
wobeia
ein Array-Objekt ist, das als "Template" verendet wird (üblicherweise mit Länge 0), also z.B.:
Person[] persArr = persons.toArray(new Person[0]);
-
Weitere Methoden wie
boolean containsAll(Collection<?> c)
,boolean addAll(Collection<? extends E> c)
,boolean removeAll(Collection<?> c)
7. Interface "List" und Implementierung "ArrayList" etc.
Eine Liste ist eine per Index geordnete, weitgehend freie Sammlung von Objekten analog zu Arrays. Ihre Größe ist standardmäßig dynamisch.
Es können Doubletten und sogar null
-Werte als reguläre Inhalte auftreten.
7.1. Interface "List"
In Java gibt es dazu das Interface java.util.List<E>
, das java.util.Collection
implementiert. Damit stehen auch die dort deklarierten Methoden zur Verfügung.
Die API-Dokumentation dazu siehe List (Java SE 17 & JDK 17) - docs.oracle.com
7.1.1. Implementierende Klasse "ArrayList"
Die vermutlich am häufigsten benutzte Implementation ist die Klasse java.util.ArrayList<E>
. Diese verwendet intern bei nötiger Array-Vergrößerung oder Verkleinerung (schrumpft auch dynamisch!!) eine sehr schnelle Methode zum Kopieren von Arrays.
Details dazu […klicken zum Auf/Zuklappen…]
static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
oder in neueren Versionen eine in der Klasse java.util.Arrays
verfügbare Variante (je nach Element-Typ) von
static <T> T[] copyOf(T[] original, int newLength)
(liefert ein neues, bereits passend gefülltes Array der gewünschten Länge).
Beide verwenden vermutlich eine in der JVM mit der Sprache C implementierte, hoch optimierte Array-Kopier-Funktionalität.
7.1.2. Verwendung
Die Listen-Variable für eine Personen-Liste wird mit
List<Person> persons;
deklariert (dann gibt es die Liste aber noch nicht, persons
als Attribut ist null
.)
Erzeugt wird das Listen-Objekt mit einer implementierenden konkreten Klasse – sehr häufig java.util.ArrayList<E>
– in der Form
persons = new ArrayList<>();
Es gibt auch Konstruktoren, die Initial-Kapazität und/oder Vergrößerungsfaktor festlegen oder einen, der eine bestehende Collection übergibt, deren Elemente in das Set (unter Beachtung u.U. gegebener Restriktionen) aufgenommen werden sollen.
7.1.3. Häufige Operationen
-
Alle Methoden, die in
Collection
deklariert sind – siehe Collection Methoden -
Holen des Elements an bestimmtem Index
idx
:E get(int idx)
, also z.B.:
Person p1 = persons.get(idx);
-
Finden der Position eines als Parameter übergebenen Objekts:
int indexOf(Object o)
, also z.B.:
int foundAt = persons.indexOf(p1);
Wenn Objekt nicht in Liste, wird-1
zurückgeliefert. -
Entfernen eines Elements an gegebener Position
idx
(falls Liste lang genug):E remove(int idx)
, also z.B.:
Person p = persons.remove(idx);
Das entfernte Element wird zurückgeliefert, um damit noch finale Arbeiten zu ermöglichen. Bei zu großem/kleinem Index wird eineIndexOutOfBoundsException
geworfen. -
Weitere Methoden:
int lastIndexOf(Object o)
,boolean isEmpty()
,void add(int index, E elem)
,boolean addAll(int idx, Collection<? extends E> c)
u.a.m. (Details siehe die oben verlinkte API-Dokumentation)
8. Interface "Set" und Implementierung "HashSet" etc.
Sets (deutsch: Mengen) sind mathematisch definiert als ungeordnete Sammlung von Elementen ohne Doubletten.
Das Interface java.util.Set
implementiert u.a. java.util.Collection
und erlaubt wegen Doublettenverbots max. 1 null
-Element
WICHTIG: man kann NICHT per Index auf ein bestimmtes Element zugreifen, da es keine Ordnung gibt.
Sets sind in vielen Zusammenhängen sehr praktisch, wo Doublettenfreiheit nötig ist. Da man in der Praxis häufig zusätzlich eine Ordnung benötigt, gibt es auch Sets, die die Einfüge-Reihenfolge beibehalten oder sortierbare Sets, wo die gewünschte Sortierung mittels "natural ordering" oder per "Comperator" als Aufruf-Parameter festlegbar ist. Siehe später unter Sortieren, "natural ordering", Komparatoren.
8.1. Interface "Set"
In Java gibt es dazu das Interface java.util.List<E>, das java.util.Collection implementiert. Damit stehen auch die dort deklarierten Methoden zur Verfügung.
Die API-Dokumentation dazu siehe Set (Java SE 17 & JDK 17) - docs.oracle.com
Sehr häufig iteriert man über ein Set. Da keine Ordnung besteht, ist ja kein Zugriff über den Index möglich, damit fällt die klassische FOR-Schleife weg. Allerdings gibt es die oben beschriebene For-Each-Schleife, die hier ihre Stärke ausspielt.
8.1.1. Häufige Operationen
Es gibt nur einige wenige zusätzliche, selten benötigte Zusatzmethoden, im wesentlichen hat man alle Methoden, die in Collection
deklariert sind, zu Verfügung – siehe Collection Methoden
8.1.2. Implementierende Klasse "HashSet"
Diese nutzt die oben genauer erläuterten HashCodes für effizienten Zugriff.
Hier wird die oben gezeigte Klasse Person verwendet (Code erst nach "Aufklappen" sichtbar! Der Konstruktor kann automatisch gneriert werden)
public class SetDemo {
public static void main(String[] args) {
Set<Person> persons = new HashSet<>();
persons.add(new Person("Kurz", "Kim", Person.Gender.FEMALE, 1999, false,"kim.kurz@you4all.at"));
persons.add(new Person("Kurz", "Kim", Person.Gender.MALE, 1999, false, "kim.kurz@just2you.at"));
persons.add(new Person("Ertl", "Evi", Person.Gender.FEMALE, 2002, true, "evi.ertl@just2you.at"));
persons.add(new Person("Eder", "Evi", Person.Gender.FEMALE, 2002, false, "evi.eder@you4all.at"));
persons.add(new Person("Alt", "Adi", Person.Gender.MALE, 1987, true, "adi.alt@you4all.at"));
printPersons(persons, "Personen-Set (Eingabe absteigend geordnet - bleibt nicht erhalten)");
}
private static void printPersons(Collection<Person> persons, String title) {
System.out.println("==== " + title + " ====");
for (Person p : persons) {
System.out.println(" " + p);
}
}
}
Die Personen wurden nach Namen absteigend hinzugefügt. Diese Ordnung bleibt nicht erhalten - Sets HABEN keine Ordnung:
==== Personen-Set (Eingabe absteigend geordnet) ==== Person: lastname='Eder', firstname='Evi', gender=FEMALE, birthYear=2002, smoker=false, email=<evi.eder@you4all.at> Person: lastname='Kurz', firstname='Kim', gender=FEMALE, birthYear=1999, smoker=false, email=<kim.kurz@you4all.at> Person: lastname='Alt', firstname='Adi', gender=MALE, birthYear=1987, smoker=true, email=<adi.alt@you4all.at> Person: lastname='Kurz', firstname='Kim', gender=MALE, birthYear=1999, smoker=false, email=<kim.kurz@just2you.at> Person: lastname='Ertl', firstname='Evi', gender=FEMALE, birthYear=2002, smoker=true, email=<evi.ertl@just2you.at>
9. Vollständiges Beispiel
Vollständiges Beispiel Lists, Sets, Maps und einigen weiteren Themen: Demo04-ListSetMap1.FULL22-1103.zip