W3docs

Java Vector

Die synchronisierte Vector-Klasse in Java, warum sie veraltet ist und wann man sie noch (selten) verwendet.

Vector<E> ist die ursprüngliche Liste mit veränderbarer Array-Größe — sie wurde in Java 1.0 eingeführt, vier Jahre bevor das Collections Framework existierte. Als Java 1.2 ArrayList hinzufügte, wurde Vector sorgfältig nachgerüstet, um List zu implementieren, damit bestehender Vector-Code nicht brach. Drei Jahrzehnte später ist er noch immer in der Standardbibliothek, funktioniert noch immer und ist für fast jeden neuen Code trotzdem die falsche Wahl. Dieses Kapitel ist bewusst kurz gehalten: Du musst wissen, was Vector ist, um ihn in altem Code zu erkennen — nicht weil du neuen Code damit schreiben wirst.

Was sich von ArrayList tatsächlich unterscheidet

Vector ist eine durch ein Array gesicherte, größenveränderliche List, genau wie ArrayList. Zwei Unterschiede sind relevant:

  • Jede öffentliche Methode ist synchronized. Jedes add, get, set, remove, size, iterator — sie alle belegen den Monitor des Vector beim Aufruf. Die Absicht im Jahr 1995 war Thread-Sicherheit; der praktische Effekt ist eine grobe, langsame und selten korrekte Sperrung auf Methodenebene (dazu unten mehr).
  • Die Wachstumsstrategie ist unterschiedlich. Standardmäßig verdoppelt Vector das Backing-Array, wenn es voll ist. ArrayList wächst um etwa 50 %. Verdoppeln verschwendet im Durchschnitt mehr Speicher; 50 % Wachstum weniger. In der Praxis spielt das keine Rolle, es sei denn, du verwaltest Millionen kleiner Listen.

Das war's. Jedes andere beobachtbare Verhalten ist gleich: O(1) wahlfreier Zugriff, O(n) Einfügen vorne, Fail-fast-Iteratoren, dasselbe generische Interface.

Warum „thread-sicher" nicht ausreicht

Das synchronized auf Methodenebene deckt genau so viel Synchronisierung ab, wie ein einzelner Aufruf benötigt — und hat genau die falsche Granularität für alles andere. Betrachte „Check-then-Act":

Vector<String> v = ...;
if (!v.contains("hello")) {     // synchronised → atomic
  v.add("hello");                // synchronised → atomic
}                                 // BUT: NOT atomic together

Zwei Vector-Aufrufe sind jeweils atomar. Die Kombination ist es nicht. Zwischen dem contains-Check und dem add kann ein anderer Thread ein konkurrierendes add einschleusen. Die gewünschte Sperre ist eine, die beide Aufrufe abdeckt, nicht jeden einzeln. Um das zu erreichen, schreibt man synchronized (v) { ... } um den gesamten Block — was genau das repliziert, was Collections.synchronizedList(arrayList) bereits tut, nur auf einer umständlicheren, älteren Klasse.

Dieselbe Falle zerstört die Iteration:

for (String s : v) { ... }   // many internal hasNext/next calls, none locked together

Eine gleichzeitige Mutation mittendrin wirft ConcurrentModificationException, genau wie bei ArrayList. Die synchronisierten Mutatoren helfen nicht; der Iterator hält die Sperre zwischen den Aufrufen nicht. Du benötigst nach wie vor ein externes synchronized (v) { ... } für sichere Iteration.

Kurz gesagt: Synchronisierung auf Methodenebene bringt sehr wenig, und die Sperrkonflikte, die sie kostet, sind real. Feinkörnige concurrent Collections im Stil von ConcurrentHashMap (CopyOnWriteArrayList, ConcurrentLinkedDeque usw.) sind das, was moderner Code verwendet.

Die Vector-eigene API, die du sehen wirst

Eine Handvoll Methoden existiert auf Vector, aber nicht auf List. Es sind veraltete Synonyme, die aus Gründen der Abwärtskompatibilität beibehalten wurden:

Vector-MethodeList-Äquivalent
addElement(E)add(E)
insertElementAt(E, int)add(int, E)
removeElement(Object)remove(Object)
removeElementAt(int)remove(int)
elementAt(int)get(int)
setElementAt(E, int)set(int, E)
firstElement() / lastElement()get(0) / get(size()-1)
elements()iterator() (gibt das ältere Enumeration zurück)
capacity()(kein Äquivalent)
copyInto(Object[])toArray()

elements() ist die Methode, die die meisten überrascht — sie gibt Enumeration<E> zurück, das Traversal-Interface aus der Zeit vor Iterator. Wenn du Code siehst, der elements() aufruft, ist das ein eindeutiges Zeichen für Vector (oder Hashtable).

Wann Vector in neuem Code akzeptabel ist

Ehrlich gesagt sehr selten. Zwei Fälle kommen vor:

  • Du wartest oder erweiterst alten Code, der ihn bereits verwendet. Ändere den umgebenden Code nicht nur, um VectorArrayList auszutauschen — der Gewinn ist den Diff nicht wert. Neuer Code im selben Modul kann ArrayList verwenden.
  • Eine API erfordert explizit Vector. Einige ältere Swing-Klassen (JTables DefaultTableModel, historisch JLists DefaultListModel) nehmen oder geben Vector zurück. Verwende, was die API an der Schnittstelle verlangt, und konvertiere danach, wenn du lieber mit einer List arbeiten möchtest.

Für „Ich brauche eine thread-sichere Liste" sind die besseren Optionen:

  • Collections.synchronizedList(new ArrayList<>()) — dasselbe Sperr-Modell auf Methodenebene, aber auf der modernen Klasse. Benötigt dennoch externe Sperren für zusammengesetzte Operationen und Iteration.
  • CopyOnWriteArrayList — sperrenfreie Lesezugriffe, sichere Iteration über einen Snapshot. Ideal für viele-Leser-wenige-Schreiber-Szenarien (Observer-Listen, Event-Listener, quasi-unveränderliche Konfigurations-Caches).

Für „Ich brauche maximale single-threaded Listenperformance" nimm ArrayList. Der synchronized-Overhead von Vector ist klein, aber nicht null, und es gibt keinen Vorteil, wenn kein anderer Thread beteiligt ist.

Ein Praxisbeispiel: ArrayList und Vector nebeneinander

Das folgende Programm zeigt das API-Spiegelbild, die veralteten Methodennamen und eine kleine Demonstration, dass synchronized auf Methodenebene nicht dasselbe ist wie eine thread-sichere zusammengesetzte Operation. Lies den Synchronisierungshinweis am Ende — das ist der eigentliche Kern dieses Kapitels.

java— editable, runs on the server

Was die Ausführung zeigt:

  • ArrayList und Vector sind über das List-Interface austauschbar — gleiche Elemente, gleiche Gleichheit, gleiche Iterationsreihenfolge.
  • Die Vector-eigenen Methoden (addElement, firstElement, elements, capacity) sind noch immer vorhanden und aktiv — deshalb sieht man sie noch in altem Code.
  • Die Race-Demonstration ist der eigentliche Grund, warum Vector als „veraltet" gilt: seine Synchronisierung hat die falsche Granularität. Die Anzahl der gespeicherten 1en ist größer als eins, weil der Check und das Add zusammen nicht atomar sind.

Was als Nächstes kommt

Der andere Überlebende aus der 1.0-Ära — aufgebaut auf Vector und mit all seinen Mängeln behaftet — ist die Stack-Klasse. Sie ist die zweite Fallstudie für „nützliche Idee, veraltete Implementierung, moderner Ersatz verfügbar." Dieser Ersatz ist Deque, den wir zwei Kapitel später kennenlernen werden.

Übungen

Übung
Ein Teamkollege schreibt `if (!vector.contains(x)) vector.add(x);`, um `x` aus mehreren Threads sicher nur einmal hinzuzufügen, mit der Begründung, dass `Vector` thread-sicher sei. Was ist tatsächlich wahr?
Ein Teamkollege schreibt `if (!vector.contains(x)) vector.add(x);`, um `x` aus mehreren Threads sicher nur einmal hinzuzufügen, mit der Begründung, dass `Vector` thread-sicher sei. Was ist tatsächlich wahr?
Was this page helpful?