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 jeder Collection übrigens über den Aufruf der Methode iterator().

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 von ArrayList<String> list = new ArrayList<String>();, oder definieren als Parameter und Attribute Ihrer Methoden und Klassen keine konkreten Implementierungen, sondern lediglich die Interfaces List, 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 dieser Collection 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 dieser Collection 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 die Collection iteriert werden kann.
  • remove(Object o) – löscht ein Objekt aus der Collection.
  • removeAll(Collection<?> c) – entfernt alle Objekte der übergebenen Collection aus dieser Collection.
  • retainAll(Collection<?> c) – löscht alle Objekte von der Collection, die nicht in der übergebenen Collection 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).

2 Replies to “08.02 Die Collection-Schnittstelle”

  1. Cyrill Brunner

    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?

  2. Stefan Kiesel

    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, …)

Schreibe einen Kommentar

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.