☕ Java
Vector
Vector is a legacy resizable-array implementation of the List interface that predates the Collections Framework. It is functionally similar to ArrayList but all its public methods are synchronised, making it thread-safe but slower than ArrayList in single-threaded use. Vector was Java's original dynamic array, introduced in Java 1.0. It has been retrofitted to implement the List interface but retains its original API alongside the List API. For new code, ArrayList should always be preferred unless thread safety is specifically required.
Vector — History and Design
Vector was one of the original Java collection classes, included since Java 1.0 (1996). At that time, the Collections Framework did not exist. Vector was designed as a thread-safe dynamic array, and its synchronisation was baked into every method. Every call to add(), get(), remove(), size(), and all other methods acquires an intrinsic lock on the Vector object, executes the operation, and releases the lock.
When the Collections Framework was introduced in Java 2 (1998), Vector was retrofitted to implement the new List interface rather than being deprecated outright. This preserved backward compatibility while integrating Vector into the framework. However, this retrofit was not clean — Vector retains several legacy methods from its pre-List API that have equivalents in the List interface. addElement() is equivalent to add(), elementAt() is equivalent to get(), removeElement() is equivalent to remove(), and removeAllElements() is equivalent to clear(). These duplicate methods exist solely for backward compatibility.
The synchronisation model of Vector is the coarse-grained kind that causes significant performance problems under contention. Every method acquires the Vector's intrinsic lock (this). When multiple threads compete for the lock, they serialise completely — only one thread can execute any Vector method at a time. This is worse than necessary for read-heavy workloads where many threads could safely read simultaneously.
Furthermore, Vector's method-level synchronisation does not make compound operations atomic. The check-then-act pattern if (!vector.isEmpty()) vector.remove(0) is not thread-safe even with a synchronised Vector, because another thread can remove the last element between the isEmpty() check and the remove() call. The lock is released between the two method calls.
The correct modern approaches for a thread-safe list are: Collections.synchronizedList(new ArrayList<>()) for an equivalent to Vector without the legacy API overhead, or CopyOnWriteArrayList for read-heavy concurrent access. Neither replaces Vector perfectly in all cases, but both are better choices for new code.
Java
// ── Vector — all methods are synchronised: ───────────────────────────
Vector<String> vector = new Vector<>();
// Legacy API (pre-Collections Framework):
vector.addElement("Alice"); // same as add()
vector.addElement("Bob");
String elem = vector.elementAt(0); // same as get(0)
int size = vector.size();
boolean found = vector.contains("Alice");
vector.removeElement("Bob"); // same as remove("Bob")
vector.removeAllElements(); // same as clear()
Enumeration<String> e = vector.elements(); // legacy Enumeration (pre-Iterator)
while (e.hasMoreElements()) {
System.out.println(e.nextElement());
}
// Modern List API also works (List interface retrofit):
vector.add("Charlie");
vector.add("Dave");
String s = vector.get(0); // same as elementAt(0)
vector.remove("Charlie"); // same as removeElement("Charlie")
vector.set(0, "Updated");
for (String item : vector) { ... } // for-each via Iterable
// ── Capacity model — similar to ArrayList: ────────────────────────────
// Vector has a capacityIncrement parameter:
Vector<Integer> v = new Vector<>(10, 5); // initial capacity 10, grow by 5
// Unlike ArrayList (grows by 50%), Vector grows by capacityIncrement
// or doubles if capacityIncrement is 0.
// ── Why Vector is slow: ───────────────────────────────────────────────
// Every method: synchronized (method) { ... }
// Even size() acquires a lock:
// public synchronized int size() { return elementCount; }
// In a single-threaded program, this lock is uncontested but still has
// overhead from the monitor acquisition machinery.
// ── Thread-safe but not enough: ──────────────────────────────────────
Vector<Integer> shared = new Vector<>();
// Still not safe for compound operations:
if (shared.isEmpty()) {
// Another thread could add an element here
shared.remove(0); // IndexOutOfBoundsException possible!
}
// Both isEmpty() and remove() are synchronised individually,
// but there is no lock held between the two calls.Vector vs ArrayList vs Collections.synchronizedList
Understanding the relationship between Vector, ArrayList, and the synchronised wrapper is important for maintaining legacy code and for making correct choices in new code.
Vector and Collections.synchronizedList(new ArrayList<>()) are functionally equivalent in terms of thread safety — both synchronise all single-method operations. The synchronised wrapper even delegates to Vector's locking model internally. The key differences are that Collections.synchronizedList returns the standard List interface without Vector's legacy API, requires explicit synchronisation around iteration just like Vector does, and can be backed by any List implementation.
ArrayList is dramatically faster than Vector in single-threaded code because it has no synchronisation overhead. Benchmarks consistently show ArrayList outperforming Vector by 20-50% for typical operations in single-threaded scenarios. For multi-threaded code where a list is shared between threads, ArrayList is not safe without synchronisation.
The choice between Vector/synchronizedList and CopyOnWriteArrayList depends on the read-write ratio. CopyOnWriteArrayList creates a new copy of the internal array on every write operation, making writes expensive (O(n)) but reads completely unsynchronised (O(1), no lock at all). For a list that is read thousands of times for each write — a list of event listeners, a configuration list that changes rarely — CopyOnWriteArrayList offers significantly better concurrent throughput than Vector.
When you encounter Vector in existing code, the safest migration path is to replace it with ArrayList and add synchronisation where needed, or to use CopyOnWriteArrayList if the usage pattern suits it. Direct find-replace of Vector with ArrayList may be unsafe if the code relied on Vector's synchronisation for thread safety — this requires careful analysis of all threads accessing the list.
Java
// ── Comparison of thread-safe list options: ──────────────────────────
// Option 1: Vector (legacy)
Vector<String> vector = new Vector<>();
// All methods synchronised — each acquires this's intrinsic lock
// Iteration still requires external synchronisation:
synchronized (vector) {
for (String s : vector) {
System.out.println(s); // must sync iteration
}
}
// Option 2: Collections.synchronizedList (modern wrapper)
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
// Equivalent semantics to Vector, cleaner API:
synchronized (syncList) {
for (String s : syncList) {
System.out.println(s); // still must sync iteration
}
}
// Option 3: CopyOnWriteArrayList (read-heavy concurrent)
CopyOnWriteArrayList<String> cowList = new CopyOnWriteArrayList<>();
cowList.add("Alice"); // creates a copy of the internal array — expensive
cowList.add("Bob"); // another copy
// But reads are completely lock-free:
for (String s : cowList) {
System.out.println(s); // no synchronisation needed — iterates a snapshot
}
// ── Performance characteristics: ──────────────────────────────────────
//
// ArrayList Vector SyncList COWArrayList
// ───────────────── ───────── ─────── ──────── ────────────
// Single thread Fastest Slow Slow Slow for write
// Read (multi-thd) UNSAFE Safe Safe Fastest
// Write (multi-thd) UNSAFE Safe Safe Slowest (copy)
// Best use case Default Legacy Replace Read-heavy
//
// ── Migrating from Vector to ArrayList: ──────────────────────────────
// Single-threaded: safe direct replacement
Vector<String> oldCode = new Vector<>();
List<String> newCode = new ArrayList<>();
// Multi-threaded: requires analysis
// If synchronisation was relied upon:
List<String> safeMigration =
Collections.synchronizedList(new ArrayList<>());
// or CopyOnWriteArrayList for read-heavy
CopyOnWriteArrayList<String> readHeavy = new CopyOnWriteArrayList<>();Related Topics in Collections Framework
Collections Overview
The Java Collections Framework (JCF) is a unified architecture for representing and manipulating groups of objects. It provides a set of interfaces that define the operations a collection must support, a set of abstract classes that provide partial implementations, and a set of concrete implementations optimised for different use cases. Every Java developer uses collections daily — lists for sequences, sets for uniqueness, maps for key-value pairs, and queues for ordering — and choosing the right implementation for the right use case is one of the most fundamental practical skills in Java.
Iterable
Iterable<E> is the root interface of the Java Collections hierarchy. Any class that implements Iterable can be used in a for-each loop. It declares a single abstract method: iterator(), which returns an Iterator<E> that the for-each loop uses to traverse the elements. Implementing Iterable is all that is required to make a custom data structure work with Java's enhanced for loop, the Stream API, and any method that accepts an Iterable.
Collection Interface
Collection<E> is the root interface of the main collection hierarchy, extending Iterable<E>. It defines the common operations that all collection types must support: adding elements, removing elements, checking containment, querying size, clearing, converting to an array, and bulk operations. List, Set, and Queue all extend Collection. Map does not extend Collection because a map operates on key-value pairs rather than individual elements.
ArrayList
ArrayList is a resizable-array implementation of the List interface. It is the most commonly used collection in Java, providing dynamic sizing on top of a standard array. Elements are stored in contiguous memory, enabling O(1) random access by index. When the internal array is full, ArrayList automatically allocates a larger array and copies all elements. ArrayList is not thread-safe and preserves insertion order, allowing duplicates.