W3docs

Java Collections Utility-Klasse

Die Collections-Klasse in Java nutzen: sortieren, suchen, umkehren, mischen und Collections kapseln.

java.util.Collections ist die Standardbibliothek mit statischen Hilfsmethoden, die auf Collections operieren. Stellen Sie sich diese Klasse wie java.util.Arrays vor: eine final-Klasse ohne Instanzzustand, nur mit statischen Methoden. Man schreibt nie new Collections() — sondern Collections.sort(list), Collections.shuffle(list), Collections.unmodifiableMap(map).

Es ist leicht, die Klasse mit der benachbarten Schnittstelle zu verwechseln: Collection<E> (Schnittstelle, mit großem C und ohne s) ist der Obertyp von List, Set und Queue; Collections (Klasse, Plural) ist der Werkzeugkasten. Die Klasse implementiert die Schnittstelle nicht — sie operiert lediglich auf Collections, die dies tun.

Eine geführte Tour durch den Werkzeugkasten

Die Methoden gliedern sich in sechs Themenbereiche. Wir behandeln jeden davon; die nächsten zwei Kapitel vertiefen das Sortieren und Suchen im Detail.

1. Ordnen und Umordnen

Collections.sort(list);                       // natural order — requires Comparable
Collections.sort(list, comparator);            // custom comparator
Collections.reverse(list);                     // in place
Collections.shuffle(list);                     // pseudo-random permutation
Collections.shuffle(list, new Random(42));     // deterministic shuffle with a seeded RNG
Collections.rotate(list, 2);                   // [a,b,c,d,e] → [d,e,a,b,c]
Collections.swap(list, 0, list.size() - 1);    // swap two indices

sort ist ein stabiles Mergesort — gleiche Elemente behalten ihre relative Reihenfolge. shuffle führt einen Fisher-Yates-Mischvorgang durch, der gleichmäßig zufällig ist, wenn es der RNG ist. rotate ist genau das Richtige, wenn man meint „alles um N Positionen verschieben, mit Umbruch an den Enden". reverse, swap und rotate verändern die Liste in-place; keine von ihnen gibt etwas Nützliches zurück.

2. Suchen

int i = Collections.binarySearch(sortedList, key);              // O(log n) — list must be sorted
int j = Collections.binarySearch(sortedList, key, comparator);
T max = Collections.max(coll);
T min = Collections.min(coll, comparator);
int n  = Collections.frequency(coll, target);                   // how many times target appears
boolean disjoint = Collections.disjoint(a, b);                  // no element in common?

binarySearch hat ein eigenes Kapitel — kurzgefasst: die Liste muss bereits in derselben Reihenfolge sortiert sein, die die Suche verwendet, und ein negativer Rückgabewert bedeutet „nicht gefunden, aber Sie können den Einfügepunkt als -result - 1 berechnen."

3. Füllen, Kopieren, Ersetzen

Collections.fill(list, "x");                                   // overwrite every slot with "x"
Collections.copy(dest, src);                                    // copy src into dest; dest.size() must be ≥ src.size()
Collections.replaceAll(list, "old", "new");                     // returns true if anything changed
Collections.nCopies(5, "x");                                    // immutable list with "x" 5 times
Collections.singleton(value);                                   // immutable Set of one
Collections.singletonList(value);                               // immutable List of one
Collections.singletonMap(k, v);                                 // immutable Map of one entry
Collections.emptyList();  Collections.emptyMap();  Collections.emptySet();

Die Fabriken für leere/Singleton/nCopies-Instanzen geben gecachte, unveränderliche Instanzen zurück — sie allozieren nicht pro Aufruf. Das ist eine kleine, kostenlose Optimierung, wenn man eine bekannte leere oder bekannte kleine Collection benötigt.

4. Synchronisierte Wrapper (hauptsächlich historisch)

List<String>      lockedList = Collections.synchronizedList(new ArrayList<>());
Map<String, Int>  lockedMap  = Collections.synchronizedMap(new HashMap<>());
Set<String>       lockedSet  = Collections.synchronizedSet(new HashSet<>());

Diese kapseln eine Collection so, dass jede Methode eine Sperre auf dem Wrapper erwirbt. Der gleiche Vorbehalt wie bei Hashtable gilt: zusammengesetzte Operationen sind weiterhin racy, und Iteratoren müssen explizit in synchronized (wrapper) { ... }-Blöcken eingeschlossen werden:

synchronized (lockedList) {
  for (String s : lockedList) { ... }       // safe: holds the lock for the whole walk
}

In modernem Code verwendet man stattdessen ConcurrentHashMap, CopyOnWriteArrayList und ConcurrentSkipListSet. Die synchronisierten Wrapper existieren, um eine nicht-threadsichere API nachträglich threadsicher zu machen, wenn nichts anderes passt.

5. Nicht-veränderliche Wrapper

List<String> frozen   = Collections.unmodifiableList(mutableList);
Set<String>  frozenS  = Collections.unmodifiableSet(mutableSet);
Map<K, V>    frozenM  = Collections.unmodifiableMap(mutableMap);

Diese kapseln eine Collection so, dass Mutationsmethoden UnsupportedOperationException werfen. Die originale Collection ist weiterhin veränderlich — der Wrapper ist eine schreibgeschützte Sicht. Änderungen über das Original sind durch die Sicht sichtbar. Das ist ein wesentlicher Unterschied zu den Fabriken List.of(...) / Set.of(...) / Map.of(...), die vollständig unveränderliche Collections mit eigenem Speicher erzeugen. Das nächste Kapitel vergleicht beide.

6. Einzelelement- und typsichere Sichten

List<Object> objects = new ArrayList<>();
List<String> safe    = Collections.checkedList(objects, String.class);
safe.add("ok");                              // fine
((List) safe).add(42);                       // throws ClassCastException immediately, not later

checkedList, checkedSet, checkedMap installieren eine Laufzeit-Typprüfung bei jeder Einfügeoperation. Nützlich in Legacy-Code, der generische Collections durch Object-typisierte APIs übergibt — der Wrapper schlägt laut an der Stelle der Einfügung fehl, statt viel später beim Abrufen.

Einige kleine, aber wertvolle Methoden

  • Collections.disjoint(a, b) gibt true zurück, wenn kein Element von a in b ist. Idiomatisch für „gibt es irgendeine Überschneidung zwischen diesen zwei Mengen?"
  • Collections.frequency(coll, target) zählt Vorkommen — viel klarer als coll.stream().filter(x -> x.equals(target)).count().
  • Collections.nCopies(n, x) ist manchmal genau das Richtige, z. B. result.addAll(Collections.nCopies(rows, "pad")). Die zurückgegebene Liste ist unveränderlich, belegt aber unabhängig von n nur O(1) Speicher — sie ist eine virtuelle Liste, kein Backing-Array.
  • Collections.reverse(list) ist in-place und stabil. Nicht selbst mit einer for-Schleife nachbauen.
  • Collections.addAll(coll, "a", "b", "c") ist kürzer und schneller als coll.addAll(List.of("a", "b", "c")), weil die Zwischenliste entfällt.

Was Collections nicht ist

  • Kein Stream-Ersatz. Für filter/map/reduce sollte man Streams verwenden. Collections befasst sich mit Mutation und direkten Abfragen, nicht mit deklarativen Pipelines.
  • Nicht der Ort für List.of / Set.of / Map.of. Das sind Fabriken auf den Schnittstellen, die in Java 9 hinzugefügt wurden. Sie liegen neben Collections.unmodifiableList, gehören aber nicht zu dieser Klasse.
  • Nicht der Ort für Stream-Collectors. Das ist java.util.stream.Collectors. Anderes Paket, andere Rolle.

Ein vollständiges Beispiel: der Werkzeugkasten in einem Programm

Das folgende Programm wendet ein Dutzend Collections-Methoden auf eine einzelne Liste und eine einzelne Map an, um die API greifbar zu machen: sort, reverse, shuffle, rotate, swap, binarySearch, min/max, frequency, disjoint, fill, replaceAll und die nicht-veränderliche Sicht.

java— editable, runs on the server

Was man aus der Ausführung mitnehmen kann:

  • Jede Methode entweder mutiert in-place (sort, reverse, shuffle, rotate, swap, fill, replaceAll) oder gibt einen primitiven Wert zurück (min, max, frequency, disjoint, binarySearch). Nichts im Werkzeugkasten gibt eine „neue" sortierte Liste zurück — Collections.sort modifiziert die übergebene Liste.
  • binarySearch hat den Index von "delta" zurückgegeben und einen negativen Wert für "zeta". Die Konvention -result - 1 ergibt den Einfügepunkt, der die Liste sortiert halten würde.
  • replaceAll hat einen String überall ersetzt, wo er vorkam; fill hat jeden Slot überschrieben. Beide arbeiten auf derselben Liste — praktisch, wenn man Speicher wiederverwenden möchte.
  • Collections.unmodifiableList(backing) hat eine schreibgeschützte Sicht zurückgegeben. Die Sicht hat bei add eine Ausnahme geworfen, aber das Mutieren der Backing-Liste funktionierte weiterhin, und die Änderung war durch die Sicht sichtbar. Die Sicht ist keine Kopie.

Was kommt als Nächstes

Der Werkzeugkasten ist nun auf Index-Ebene im Kopf. Zwei Operationen verdienen einen näheren Blick, weil ihre Details wichtig sind: Sortieren von Java-Collections (wann Collections.sort vs. List.sort vs. stream().sorted(), stabile Reihenfolge, Comparator-Builder, primitive Spezialisierungen) und Suchen in Java-Collections (contains, indexOf, binarySearch und streambasierte Suche). Das nächste Kapitel behandelt das Sortieren.

Übungen

Übung
Sie sortieren eine `List<String>` mit `Collections.sort(list)` und rufen dann `Collections.binarySearch(list, 'zeta')` auf; das Ergebnis ist `-4`. Was bedeutet `-4`?
Sie sortieren eine `List<String>` mit `Collections.sort(list)` und rufen dann `Collections.binarySearch(list, 'zeta')` auf; das Ergebnis ist `-4`. Was bedeutet `-4`?
Was this page helpful?