Kompakt-Einstieg Java Collections mit Generics, For-Each, Iteratoren
1. Überblick
Java Collections sind Sammlungen von Objekten mit gemeinsamer Charakteristik (gemeinsame Klasse / gemeinsames Interface). Passen normalerweise Kapazität dynamisch an, Daten primitiver Typen müssen Wrapper-Klasse verwenden.
Sie sind als Interface java.util.Collection<E>
definiert.
Die 2 mit weitem Abstand gebräuchlichsten Sub-Interfaces sind java.util.List<E>
und java.util.Set<E>
Die wichtigsten Implementationen sind:
Deklarationstyp List<E>
:
-
Erzeugung als
java.util.ArrayList<>
(meistens verwendet) -
Erzeugung als
java.util.LinkedList<>
(gelegentlich verwendet – schneller bei häufigen Änderungs-Operationen im vorderen Bereich)
Deklarationstyp Set<E>
:
-
Erzeugung als
java.util.HashSet<>
(meistens verwendet) -
Erzeugung als
java.util.LinkedHashSet<>
(Beibehaltung Einfüge-Reihenfolge – gelegentlich benötigt)
Deklarationstypen java.util.SortedSet<E>
(Sub-Interface von Set) und
sein Sub-Interface java.util.NavigableSet<E>
(zusätzlich nächst-kleinerer/-größerer Nachbar etc.):
-
Erzeugung als
java.util.TreeSet<>
(automatisch sortiert nach natural ordering oder perjava.util.Comparator<T>
-Objekt)
2. Operationen / Methoden im Interface Collection
Folgende grundlegende Operationen sind für alle Sub-Interfaces normalerweise vorhanden (und "exotischere" weitere):
-
Elementanzahl erfragen:
int size()
,boolean isEmpty()
-
Enthalten-Sein von Elementen prüfen:
boolean contains(Object o)
,boolean containsAll(Collection<?> c)
-
Hinzufügen von Elementen (optional):
boolean add(E e)
,boolean addAll(Collection<? extends E> c)
-
Entfernen von Elementen (optional):
boolean remove(Object o)
,boolean removeAll(Collection<?> c)
,void clear()
(leert komplett) -
Inhaltsgleiches Array erzeugen:
<T> T[] toArray(T[] a)
(a
ist "Template"-Array-Objekt, sinnvoll mit Kapazität 0 oder aber zur direkten Nutzung mit benötigter Kapazität) -
Iterator-Objekt für das Collection-Objekt:
Iterator<E> iterator()
-
Per Konstruktor oder Utility-Klassen
java.util.Collections
(Endung s) gibt es Möglichkeiten, neueCollection
-Instanzen mit Inhalt einer übergebenenCollection
zu erzeugen (wichtig, wenn unveränderbar) -
Utility-Klasse
java.util.Arrays
hat Methodestatic <T> List<T> asList(T… a)
.
SelbstSet
-Implementationen haben oft Konstruktoren, die alleCollection
-Arten als Parameter erlauben.
T…
bedeuet, dass sowohl Arrays als auch explizite Wert-Aufzählungen erlaubt sind. Durch Autoboxing ist auch folgendes möglich:
List<Double> l = Arrays.asList(1.0, 2.0, 3.0);
Manche Implementationen verbieten z.B. Änderungs-Operationen (werfen UnsupportedOperationException
) – Kennzeichnung optional
Für Details siehe: zu Collection sowie zu List und zu Set.
3. Generics
Generics erlauben die Angabe von Typ-Parametern – in Collections kann damit der Element-Typ festgelegt werden.
-
Deklaration als
List<E>
erfordert Verwendung in Form von z.B.List<Person>
. Dies erzwingt ausschließlich Personen-Objekte als Inhalt. -
Es können mehrere Typen beistrich-getrennt nötig sein: Deklaration
Map<K, V>
kann alsMap<SozVersNr, Person>
verwendet werden -
Üblicherweise werden bei Deklaration Einzel-Großbuchstaben wie
T
(type),E
(element),K
(key),V
(value) verwendet. -
Deklaration
<?>
bedeutet: beliebiger Typ -
Deklaration
<T extends Person>
bedeutet:T
mussPerson
oder davon abgeleitete Klasse sein. -
Deklaration
<T super Tier>
bedeutet:T
muss Tier oder eine Superklasse sein. -
Deklarationen können verschachtelt sein:
<T extends Object & Comparable<? super T>>
Gute Info-Quellen:
Generics - www.torsten-horn.de
AngelikaLanger.com - Java Generics - Einführung
4. equals(..) und hashCode()
Diese dienen zur Objekt-Identifikation. (equals(..)
) bzw. zur effizienten Auffindung in Objekt-Sammlungen (hashCode()
) und sind schon innerhalb der Klasse Object
erstmalig definiert.
Die Methoden-Signatur sind: boolean equals(Object obj)
und int hashCode()
.
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.
HashCodes sind int
-Werte, die aus einer Auswahl von Objekt-Attributen einfach berechnet werden und normalerweise innerhalb der Collection
NICHT eindeutig sind. Sie sollen vielmehr die Elemente einer Collection in viele kleine Gruppen mit selbem HashCode gliedern.
Die Elemente werden nach HashCode-Wert sortiert abgelegt und bei Prüfung, ob ein gegebenes Objekt in der Collection enthalten ist, wird dessen HashCode berechnet und dann die Position der Elemente mit diesem HashCode gesucht. Da die Ablage nach HashCodes geordnet ist, kann die Position extrem effizient gefunden werden. An dieser Stelle müssen im Normalfall nur sehr wenige Elemente mit equals(..)
geprüft werden, bis das tatsächlich gesuchte Element gefunden ist.
Hier zeigt sich, dass equals(..)
und hashCode()
zueinander konsistent definiert sein müssen, da sonst das gesuchte Objekt u.U. gar nicht im erwarteten Bereich liegt und die Auffindung fehlschlägt.
Beide Methoden lassen sich gut automatisiert und konsistent erstellen – praktisch alle IDEs bieten eine solche Funktionalität an.
Hier finden sich Details zu equals(..)
und Details zu hashCode()
sowie [Beispiel-Code]
5. For-Each
Auch "enhanced for loop" genannt, dient zu Iteration über ("Wiederholen", Durcharbeiten von) Arrays und Collections und generell über alle Klassen, die Interface java.lang.Iterable
(siehe unten) implementieren.
----->
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) { // color erhält in jedem Durchlauf anderen Wert
System.out.format("%s%s", separator, color);
separator = ", ";
}
System.out.println("]");
----
Die im Kopf des Statements deklarierte Variable color
erhält in jeder Runde einen weiteren Wert des Arrays, bis alle durchlaufen sind.
Wichtig ist, dass z.B. bei Sets keine Ordnung definiert ist, es werden also zwar alle Elemente des Set bereitgestellt, die Reihenfolge ist jedoch nicht definiert!
6. Iteratoren
Alle Klassen, die das Interface java.lang.Iterable
implementieren, müssen Methode Iterator<T>
iterator()
implementieren. Interface java.util.Iterator<E>
(E
ist der generic Element Typ) gehört zum Collection
Framework.
Folgende Methoden sind essentiell:
-
boolean hasNext()
: "schnuppert, ob weiteres Element verfügbar" -
E next()
: liefert nächstes (zu Beginn erstes) Element -
default void remove()
: erlaubt es im Gegensatz zum Durchlaufen einer for-each-Schleife SICHER, während der Iteration beliebig viele Elemente zu entfernen.
Beispiel:
public class IteratorDemo
{
public static void iteratorRemoveDemo() {
Set<String> msgSet = new HashSet<>(Arrays.asList("Hi", " ", "i", "", "am", "somewhere"));
printStringColl("nach Erzeugung:", msgSet);
Iterator<String> msgSetIterator = msgSet.iterator();
while (msgSetIterator.hasNext()) {
String msg = msgSetIterator.next();
if (msg.isBlank()) {
msgSetIterator.remove(); // no Problem to remove several
System.out.format("Removed (iterator) from Set: '%s'%n", msg);
}
}
printStringColl("nach Bereinigung:", msgSet);
}
public static void printStringColl(String title, Collection<String> coll) {
System.out.print(title);
for (String el : coll) {
System.out.format(" '%s'", el);
}
System.out.println();
}
}