08.02 Die Collection-Schnittstelle
Das java.util.Collection
-Interface ist die Mutter aller einfachen Datenstrukturen. Auf ihr basieren sämtliche untergeordneten Schnittstellen, die eine Gruppe von Objekten eines beliebigen Datentyps beinhalten. Es gibt im Standard-Java allerdings keine Klasse, die java.util.Collection
direkt implementiert. Meistens werden Subinterfaces wie die List oder das Set eingesetzt.
Eine Collection
enthält (wie bereits erwähnt) mehrere Objekte eines beliebigen Datentyps, welcher sich durch den generischen Typ der Collection
definiert. Dabei wird sie – abhängig von der Anzahl der Objekte – automatisch größer und kleiner. Collections
sind also dynamischer als Arrays. Ob ein und das selbe Objekt mehrmals in einer Collection
vorkommen darf, hängt von der jeweiligen Implementierung ab. Dennoch bieten Collections
diverse allgemeine Methoden zur Manipulation. Somit ist die Collection
-Schnittstelle eher für solche Fälle gedacht, in denen ein hoher Grad an Generalisierung notwendig ist.
Zum Beispiel könnten Sie sich auf diese Art eine Methode schreiben, die die Objekte einer beliebigen Objekt-Gruppe nacheinander ausgibt. Da Collection
auch java.util.Iterable
implementiert, können Sie einfach über eine Collection
iterieren.
public void printCollection(Collection<?> coll) { for (Object o : coll) { System.out.println(o.toString()); } }
Den
Iterator
erhalten Sie in jederCollection
übrigens über den Aufruf der Methodeiterator()
.
Auf diesen Weg könnten Sie sich den Inhalt einer beliebigen Implementation der Collection
oder eines Subinterfaces ausgeben lassen. Eine solche Implementation ist z. B. die java.util.ArrayList
, welche im nachfolgenden Kapitel noch ausführlich besprochen wird.
List<String> list = new ArrayList<String>(); list.add("Eins"); list.add("Zwei"); list.add("Drei"); printCollection(list);
Falls eine eigene Klasse von Ihnen java.util.Collection
implementiert, sollte diese laut Java API-Dokumentation einen leeren Konstruktor implementieren. Zusätzlich ist noch ein zweiter Konstruktor erwünscht, dem Sie eine weitere Collection
übergeben können. Den Inhalt dieses Parameters sollte auf Ihr Objekt übertragen werden. Beispiel:
public class MyCollection<E> implements Collection<E> { public MyCollection () {} public MyCollection(Collection<E> newCollection) { this.addAll(newCollection); // Eine Collection muss die Methode "addAll" implementieren. } // Implementierung der restlichen Methoden }
Folgend finden Sie eine Auflistung aller Methoden einer Collection
. Beachten Sie, dass Funktionen und Verfügbarkeit der Methoden stark von der jeweiligen Implementation abhängig ist. So werfen bestimmte Implementierungen bei gewissen Aufrufen diverse Fehlermeldung. Z. B. eine ClassCastException
, NullPointerException
oder UnsupportedOperationException
. Diese werden zwar in der Dokumentation als solche gekennzeichnet, trotzdem sollten Sie sich – sofern Sie nicht gegen eine Schnittstelle programmieren – die genauen Möglichkeiten der eingesetzten Collection
durchlesen.
Wenn Sie gegen eine Schnittstelle programmieren, schreiben Sie bspw.
List<String> list = new ArrayList<String>();
anstelle vonArrayList<String> list = new ArrayList<String>();
, oder definieren als Parameter und Attribute Ihrer Methoden und Klassen keine konkreten Implementierungen, sondern lediglich die InterfacesList
,Set
,Map
,Collection
, …. Dies hat den Vorteil der höheren Generalisierung und Kompatibilität, aber auch den Nachteil von fehlenden Methoden und Funktionsweisen konkreter Klassen.
- add(E e) – fügt der
Collection
ein Objekt hinzu. - addAll(Collection<? extends E> c) – fügt alle Elemente der übergebenen
Collection
dieserCollection
hinzu. - clear() – löscht alle Objekte.
- contains(Object o) – überprüft, ob das übergebene Objekt enthalten ist.
- containsAll(Collection<?> c) – überprüft, ob alle Objekte der übergebenen
Collection
in dieserCollection
enthalten sind. - isEmpty() – testet, ob diese
Collection
keine Objekte enthält, also leer ist. - iterator() – wie bereits erwähnt gibt diese Methode einen
java.util.Iterator
zurück, mit welchem über dieCollection
iteriert werden kann. - remove(Object o) – löscht ein Objekt aus der
Collection
. - removeAll(Collection<?> c) – entfernt alle Objekte der übergebenen
Collection
aus dieserCollection
. - retainAll(Collection<?> c) – löscht alle Objekte von der
Collection
, die nicht in der übergebenenCollection
enthalten sind. - size() – gibt die Anzahl der Elemente in der
Collection
zurück. - toArray() – wandelt die
Collection
in ein Object-Array um. - toArray(T[] a) – wandelt die
Collection
in ein Array des übergebenen Typs um.
Zusätzlich sollten die Methoden equals
und hashCode
implementiert werden.
Mit Ihrem jetzigen Wissensstand sollten Sie die Methoden ohne Probleme nachvollziehen können. Lediglich die letzte (toArray(T[] a)
) ist vielleicht etwas unverständlich. Zurückgegeben wird immer ein Array des übergebenen Typs mit allen Daten der Collection
. Wie viele Plätze das Array enthält, welches Sie der Methode übergeben, ist egal. Merken Sie sich aber: Enthält das Array weniger Platz als die Collection
, bleiben die Elemente des Arrays mit Ihrem zuletzt zugeordnetem Wert erhalten. Ist aber ausreichend Platz für alle Elemente der Collection
im Array verfügbar, werden die Werte im Array mit denen aus der Collection
überschrieben bzw. die überflüssigen auf den Initialisierungswert gesetzt.
List<String> list = new ArrayList<String>(); list.add("Eins"); list.add("Zwei"); list.add("Drei"); String[] str = new String[2]; list.toArray(str); System.out.println(str.length); System.out.println(str[2]); str = list.toArray(new String[0]); System.out.println(str.length);
Vielleicht wundern Sie sich, dass eine Collection
– mal abgesehen von dem Iterator
– keine Möglichkeit bietet, die Objekte in ihr wieder abzufragen. Dies wurde gänzlich den Subinterfaces von Collection
überlassen, welche Sie in den nachfolgenden Kapiteln kennen lernen. Da ohnehin keine Standard-Java-Klasse Collection
direkt implementiert (siehe Einleitung), kann auf diese Methode auch vorerst problemlos verzichtet werden.
Beachten Sie auch, dass eine Collection
synchronisiert (z. B. java.util.Vector
) sein kann, dies aber keine Vorschrift ist (z. B. java.util.ArrayList
).
Ich hab eine Frage zu Sets, Lists und Maps allgemein: Ist es besser, sie als Sets, Lists und Maps zu speichern oder nimmt man am besten den Typ, von dem man dann instanziiert? Also List/Set/Map oder z.b. ArrayList/HashSet/HashMap?
Das kommt auf den Fall an. Wenn ich eine möglichst generische, von der konkreten Implementierung losgelöste Schnittstelle anbieten möchte, verwende ich als Typen die jeweiligen Interfaces. Werden bestimmte Methoden benötigt (bspw. trimToSize von ArrayList) ist es natürlich sinnvoller direkt mit einer ArrayList und nicht mit dem Interface zu arbeiten. Außerdem kann es für jemanden, der deine Schnittstelle verwendet, aus Performancegründen notwendig sein, die konkrete Implementierung zu kennen (Laufzeittechnisch existieren Unterschiede zwischen LinkedList und ArrayList, zwischen HashSet und TreeSet, …)