☕ 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
LinkedHashMap
LinkedHashMap extends HashMap and maintains a doubly-linked list running through all its entries, preserving the order in which entries were inserted. Iteration over a LinkedHashMap always returns entries in insertion order. Optionally, it can be constructed in access-order mode where entries are ordered by most-recently accessed, making it the perfect foundation for implementing a Least Recently Used (LRU) cache. LinkedHashMap has slightly higher memory overhead than HashMap due to the extra linked list pointers.
TreeMap
TreeMap is a Red-Black tree implementation of the NavigableMap interface. It stores key-value pairs in sorted order — either the natural ordering of keys (requiring them to implement Comparable) or a custom Comparator provided at construction. All basic operations (get, put, remove, containsKey) are O(log n). TreeMap provides rich navigation operations: finding the closest key, extracting sub-maps, and headMap/tailMap views.
Hashtable
Hashtable is a legacy hash table implementation that predates the Collections Framework. Like Vector for lists, Hashtable was Java's original hash map from Java 1.0. It is thread-safe because all its methods are synchronised, but it does not permit null keys or null values. Hashtable has been retrofitted to implement the Map interface but should never be used in new code — HashMap is the non-concurrent replacement and ConcurrentHashMap is the concurrent replacement.
ConcurrentHashMap
ConcurrentHashMap is a thread-safe, high-performance hash map optimised for concurrent access. Unlike Hashtable and synchronised HashMap (which use a single lock), ConcurrentHashMap uses a more sophisticated concurrency mechanism — in Java 8+, it uses CAS (compare-and-swap) operations and per-bucket synchronisation — allowing multiple threads to read and write to different parts of the map simultaneously without blocking each other. It does not permit null keys or null values.