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. Jedesadd,get,set,remove,size,iterator— sie alle belegen den Monitor desVectorbeim 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
Vectordas Backing-Array, wenn es voll ist.ArrayListwä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 togetherZwei 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 togetherEine 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-Methode | List-Ä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
Vector→ArrayListauszutauschen — der Gewinn ist den Diff nicht wert. Neuer Code im selben Modul kannArrayListverwenden. - Eine API erfordert explizit
Vector. Einige ältere Swing-Klassen (JTablesDefaultTableModel, historischJListsDefaultListModel) nehmen oder gebenVectorzurück. Verwende, was die API an der Schnittstelle verlangt, und konvertiere danach, wenn du lieber mit einerListarbeiten 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.
Was die Ausführung zeigt:
ArrayListundVectorsind über dasList-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
Vectorals „veraltet" gilt: seine Synchronisierung hat die falsche Granularität. Die Anzahl der gespeicherten1en 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.