📦 Section 4 · Question #7

What is Vector and how does it differ from ArrayList

While Vector and ArrayList share a near-identical internal data structure (both encapsulate an Object[] buffer), Vector is thread-safe by default, synchronizing nearly all of it...


🟢 Junior Level

Vector is a legacy dynamic array class introduced in Java 1.0, prior to the standardization of the Java Collections Framework (JCF) in Java 1.2. In Java 1.2, Vector was retrofitted to implement the java.util.List interface.

While Vector and ArrayList share a near-identical internal data structure (both encapsulate an Object[] buffer), Vector is thread-safe by default, synchronizing nearly all of its public methods with the synchronized keyword.

Core Comparison Matrix

Architectural Feature Vector (Legacy JDK 1.0) ArrayList (Modern JDK 1.2+)
Thread Synchronization Synchronized (methods acquire this monitor) Unsynchronized (Lock-free single-thread)
Execution Performance Slow (unnecessary monitor acquisition overhead) Blazing Fast (zero locking overhead)
Default Resizing Factor Doubles capacity ($2.0\times$) Scales by $1.5\times$ (old + (old >> 1))
Configurable Growth Step Supports explicit capacityIncrement Fixed $1.5\times$ factor
Traversal Mechanics Supports legacy Enumeration and modern Iterator Supports Iterator and Spliterator
Current Best Practice Obsolete / Deprecated in spirit Standard production default
// ❌ OBSOLETE ANTI-PATTERN:
List<String> vector = new Vector<>();

// ✅ MODERN PRODUCTION STANDARD (Single-threaded):
List<String> list = new ArrayList<>();

// ✅ MODERN PRODUCTION CONCURRENT ALTERNATIVES:
List<String> cowList = new CopyOnWriteArrayList<>();
List<String> syncList = Collections.synchronizedList(new ArrayList<>());

🟡 Middle Level

1. The Fallacy of Built-In Thread-Safety: Check-Then-Act Race Conditions

A pervasive misconception is that Vector makes multi-threaded business logic automatically thread-safe.

Synchronizing individual methods guarantees only that isolated invocations (a single get() or a single add()) execute atomically. It provides zero protection for compound operations (Check-Then-Act sequences):

// ❌ RACE CONDITION: Vector does NOT prevent this failure!
if (!vector.isEmpty()) {
    // Thread B can intervene here and execute vector.clear() or vector.remove(0)!
    String item = vector.remove(0); // 💥 Throws ArrayIndexOutOfBoundsException!
}

// ❌ RACE CONDITION on duplicate verification:
if (!vector.contains(target)) {
    // Thread B can insert target before Thread A proceeds!
    vector.add(target); // Results in duplicate entry!
}

To ensure thread safety for compound operations, the calling code must wrap the sequence in an external synchronized block:

synchronized (vector) {
    if (!vector.isEmpty()) {
        String item = vector.remove(0);
    }
}

If developers must manage external synchronization regardless, Vector’s internal per-method monitor locks serve only as redundant performance bottlenecks.


2. Iteration Vulnerability: ConcurrentModificationException

Iterating over a Vector across multiple threads is not inherently safe:

// ❌ THROWS ConcurrentModificationException if another thread mutates the vector!
for (String item : vector) { // Invokes vector.iterator() under the hood
    process(item);
}

The iterator() method in Vector returns a standard Fail-Fast iterator that inspects modCount on each step. Because the iterator does not hold the Vector monitor lock throughout the entire duration of the loop, an interleaved write by another thread causes the iterator to immediately abort with ConcurrentModificationException. Multi-threaded iteration requires explicit external synchronization:

synchronized (vector) {
    for (String item : vector) {
        process(item);
    }
}

3. Resizing Differences: $2.0\times$ vs. $1.5\times$

  • Vector Resizing Logic:
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);
    

    Unless configured with a custom capacityIncrement, Vector doubles its buffer size ($2.0\times$), causing aggressive, wasteful heap allocation.

  • ArrayList Resizing Logic: Scales by $1.5\times$, optimizing heap buffer reuse and reducing memory footprint.

🔴 Senior Level

1. Lock Contention and Reader-Thread Serialization

In Vector, all read and write methods synchronize on the same intrinsic instance monitor (synchronized (this)):

  • If 50 concurrent worker threads execute read-only queries (vector.get(i)), they cannot execute concurrently.
  • The threads are forced into a sequential bottleneck at the CPU level, experiencing severe Lock Contention and destroying application throughput.

Modern High-Performance Alternatives

  1. CopyOnWriteArrayList: Eliminates locks entirely for reads (Lock-Free via snapshot iteration). Mutations copy the backing array. Ideal for read-heavy workloads (99% reads, 1% writes) like security filter chains or event subscriber registries.
  2. Concurrent Queues (ConcurrentLinkedQueue, ArrayBlockingQueue): Utilize CAS (Compare-And-Swap) atomic instructions or fine-grained dual locks (takeLock and putLock) with condition variables for high-throughput concurrency.

2. The Architectural Flaw: Stack extends Vector (LSP Violation)

Java 1.0 introduced java.util.Stack as an inheritance-based extension of Vector: public class Stack<E> extends Vector<E>.

This design represents a classic violation of the Liskov Substitution Principle (LSP) and the Single Responsibility Principle (SRP):

Stack<String> stack = new Stack<>();
stack.push("Bottom");
stack.push("Top");

// ❌ CATASTROPHIC VIOLATION OF LIFO INVARIANT:
// Because Stack inherits all Vector methods, callers can bypass the stack top!
stack.add(1, "Injected Middle Element"); // Arbitrary mid-list insertion!
stack.remove(0);                       // Removes element from bottom of stack!
  • A stack is not a substitutable subtype of an indexed array; arbitrary positional insertions destroy the fundamental LIFO invariant.
  • The Modern Remedy: Always utilize the Deque interface implemented by ArrayDeque:
    Deque<String> stack = new ArrayDeque<>();
    stack.push("Item");
    String top = stack.pop();
    

3. Enumeration vs. Iterator

Vector retains the legacy elements() method returning java.util.Enumeration<E>:

Dimension Enumeration<E> (JDK 1.0) Iterator<E> (JDK 1.2+)
Method Names Verbose (hasMoreElements(), nextElement()) Concise (hasNext(), next())
Modification Detection Not Fail-Fast (does not check modCount) Fail-Fast (throws ConcurrentModificationException)
Element Removal Unsupported (read-only traversal) Supported via iterator.remove()
Reliability May loop indefinitely or return corrupted data on concurrent mutation Fails fast to expose concurrency bugs early

4 Tricky Questions

1. Why does synchronizing every individual method in Vector fail to make compound operations thread-safe?

Answer: Individual method synchronization guarantees atomicity only for the duration of a single method invocation.

Real-world business logic almost always executes compound operations (Check-Then-Act or Read-Modify-Write patterns), such as:

if (vector.size() > 0) {
    String item = vector.get(0);
}

Between the completion of vector.size() and the invocation of vector.get(0), the thread releases the monitor lock on the vector. Another thread can preempt execution and remove the element. When the first thread resumes and invokes get(0), it throws an ArrayIndexOutOfBoundsException.

Because per-method locks cannot span multiple method calls, developers must synchronize the entire composite block externally (synchronized (vector)), rendering the built-in per-method synchronization redundant and inefficient.


2. Why can iterating over a Vector throw ConcurrentModificationException even though Vector is marketed as a thread-safe synchronized collection?

Answer: Calling vector.iterator() returns an implementation of Iterator that tracks the collection’s modCount structural mutation counter.

While the individual methods of Vector (add, remove, get) are synchronized, the Iterator object itself does not hold the Vector monitor lock continuously across the entire lifecycle of a for-each loop.

If Thread A begins iterating through the vector and Thread B executes vector.add(e) concurrently, Thread B acquires the lock, mutates the array, increments modCount, and releases the lock. When Thread A advances to the next step (it.next()), the iterator detects a discrepancy between its expected mod count and the vector’s actual modCount, immediately throwing a ConcurrentModificationException. Safe multi-threaded iteration over Vector requires holding an explicit external lock on the vector instance for the entire loop.


3. How does java.util.Stack’s inheritance from Vector violate the Liskov Substitution Principle (LSP), and how is this resolved in modern Java?

Answer: The Liskov Substitution Principle dictates that subtypes must preserve the behavioral contracts and invariants of their base types.

By defining Stack<E> extends Vector<E>, Java 1.0 made Stack an “is-a” dynamic array. Consequently, Stack inherits all public indexed manipulation methods from Vector (add(index, element), remove(index), insertElementAt()). This allows client code to insert or remove elements at arbitrary positions in the middle or bottom of the stack, completely breaking the fundamental Last-In-First-Out (LIFO) invariant.

Modern Java resolves this architectural flaw by providing the Deque interface (implemented by ArrayDeque). ArrayDeque enforces LIFO behavior via dedicated stack methods (push, pop, peek) while omitting arbitrary indexed insertion methods, maintaining structural integrity without synchronization overhead.


4. What are the key differences between Enumeration and Iterator in the context of Vector?

Answer: Enumeration is a Java 1.0 legacy cursor returned by vector.elements(), whereas Iterator was introduced in Java 1.2 as part of the unified JCF.

  1. Fail-Fast Mechanics: Iterator is Fail-Fast. It monitors modCount and immediately throws ConcurrentModificationException if the underlying vector is structurally modified during traversal. Enumeration is not Fail-Fast; if the vector is mutated concurrently, it fails silently, risking infinite loops, skipped elements, or corrupted reads.
  2. Mutation Support: Iterator defines a safe remove() method to delete the current element during traversal. Enumeration has no deletion capabilities.
  3. API Ergonomics: Iterator uses modern method naming (hasNext() / next()), whereas Enumeration uses verbose naming (hasMoreElements() / nextElement()).

🎯 Interview Cheat Sheet

30-Second Summary

“Vector is an obsolete JDK 1.0 predecessor to ArrayList where every public method is synchronized on the instance monitor (this). While structurally identical to ArrayList, its per-method synchronization incurs substantial lock overhead and causes extreme thread contention on parallel reads, while failing to protect compound check-then-act operations without external synchronization. Additionally, Vector doubles capacity ($2.0\times$) during resizing, accelerating heap fragmentation compared to ArrayList’s $1.5\times$ growth. Modern Java uses ArrayList for single-threaded tasks, CopyOnWriteArrayList for read-heavy concurrency, and ArrayDeque for stacks.”

Common Follow-up Questions

  • “How do you make an ArrayList thread-safe?” → Use Collections.synchronizedList(new ArrayList<>()), which decorates methods with synchronization, or use CopyOnWriteArrayList for read-intensive concurrent workloads.
  • “Why is ArrayDeque preferred over Vector or Stack?” → ArrayDeque uses an unsynchronized resizable circular array, eliminating monitor lock contention and node allocations while strictly preserving FIFO/LIFO semantics.
  • “Does Vector implement RandomAccess?” → Yes, because it is backed by an array, Vector implements RandomAccess to support constant-time $O(1)$ indexed reads.

Red Flags (DO NOT Say)

  • ❌ “Vector is fully thread-safe, so you never need synchronized blocks with it.” (Compound check-then-act operations still require external synchronized locks).
  • ❌ “Vector is the recommended thread-safe list in modern Java.” (It is an obsolete legacy class; use CopyOnWriteArrayList or concurrent queues).
  • ❌ “Iterating through a Vector is thread-safe by default.” (Its iterator is fail-fast and throws ConcurrentModificationException if modified concurrently).