1. Introduction to Vector
Vector<E> is a legacy resizable-array class from Java’s original java.util library. It stores elements in an array, supports indexed access, and grows when more space is needed. Its role is similar to ArrayList, but Vector synchronizes its public methods.
Vector was introduced in Java 1.0. When the Collections Framework was added in Java 1.2, Vector was adapted to implement the List<E> interface. It therefore works with common collection operations and can be used wherever a List is expected.
Vector remains available for compatibility with older code and APIs. For most new applications, ArrayList is the usual choice when synchronization is unnecessary. When concurrent access must be supported, the appropriate modern option depends on the program’s read and write patterns.
2. What Vector Provides
Vector supports familiar list operations such as:
add(element)to append an element.get(index)to retrieve an element.set(index, element)to replace an element.remove(index)orremove(object)to remove by position or value.size()to count stored elements.indexOf(object)to find the first matching index.
Like other lists, Vector preserves element order and permits duplicates. It uses zero-based indexing, so the first element is at index 0.
Vector also retains older methods from before the Collections Framework, including elementAt, addElement, and removeElementAt. These overlap with modern methods such as get, add, and remove. They remain for backward compatibility; new code generally benefits from using the standard collection API consistently.
3. How Vector Stores Elements
Vector uses a resizable array internally. Its implementation maintains an array of element references and a count of how many entries are currently in use.
Two concepts describe its storage:
- Size: the number of elements stored in the Vector.
- Capacity: the number of slots in the backing array.
Capacity may exceed size, leaving spare slots for future additions. When the backing array becomes full, Vector allocates a larger array, copies the existing references, and switches to the new storage.
The conceptual structure is similar to ArrayList. The important differences are Vector’s synchronization behavior and its configurable capacity-increment policy.
4. Constructors and Capacity Growth
Vector provides constructors that let code choose its initial storage and, optionally, how the capacity increases.
| Constructor | Initial capacity | Growth behavior |
|---|---|---|
new Vector<>() | Historically 10 | Doubles when full under the default policy |
new Vector<>(50) | 50 | Doubles when full under the default policy |
new Vector<>(16, 8) | 16 | Adds 8 slots per growth |
new Vector<>(16, 0) | 16 | Uses the default doubling policy |
The second argument in the two-argument constructor is the capacity increment. A positive value means the array grows by that fixed amount when necessary. A value of zero uses the class’s default growth behavior.
This is a legacy flexibility that is rarely needed in modern application code. A very small fixed increment can cause repeated array allocations and copies as the collection grows. For example, growing by ten slots for every ten new elements can result in substantial cumulative copying for a large Vector.
4.1 Why growth strategy matters
When the backing array is full, a resize must copy the existing element references into a new array. With geometric growth such as doubling, these occasional copies are spread across many appends, so append operations have amortized \(O(1)\) complexity.
With a small fixed increment, the list may resize much more often. If a program has a large expected number of elements, provide a sensible initial capacity and avoid an unnecessarily small fixed increment.
5. Understanding capacity() and size()
Vector exposes both size() and capacity(), unlike ArrayList, which does not provide a public capacity accessor.
size()returns the number of actual elements.capacity()returns the length of the backing array.
If a Vector has size 2 and capacity 4, only indices 0 and 1 contain list elements. The remaining two slots are reserved storage, not extra list entries.
This distinction matters because valid indexed access is determined by size, not capacity. Calling get(2) when size is 2 is invalid even if the backing array has spare capacity.
6. Synchronization: What It Means
Vector synchronizes its public methods. A synchronized instance method acquires the monitor lock associated with that Vector before executing the method body and releases the lock when the call finishes.
If one thread is inside a synchronized method on a Vector, another thread attempting to enter a synchronized method on the same object must wait for the lock. This serializes those method calls.
The synchronization was intended to make individual operations safe to share across threads. However, synchronization is not free, and one lock around every operation can limit concurrency.
7. Why Vector’s Thread Safety Has Limits
Vector’s synchronization should not be confused with making every multi-step task automatically safe.
7.1 Synchronization overhead
Even in a single-threaded program, synchronized method calls may involve lock-management overhead. As a result, Vector can be slower than ArrayList for similar workloads, although exact performance depends on the JVM and execution environment.
7.2 Contention between threads
Because the methods use the same object monitor, threads can block one another even when they want to access different elements. The locking is coarse-grained rather than independently protecting different parts of the list.
7.3 Compound operations are not automatically atomic
Consider a task that checks whether a list is empty and then reads its last element. Even if each individual call is synchronized, another thread can change the Vector between the calls. The sequence as a whole is not protected by the individual method locks.
When a multi-step operation must be atomic, the whole sequence needs an appropriate synchronization strategy. This is true even when the underlying collection synchronizes individual methods.
The key lesson is that method-level synchronization protects each call’s execution, not necessarily the program-level invariant that spans several calls.
8. Vector Versus ArrayList
| Aspect | Vector | ArrayList |
|---|---|---|
| Introduced | Java 1.0 | Java 1.2 |
| Internal structure | Resizable array | Resizable array |
Implements List | Yes | Yes |
| Indexed access | \(O(1)\) | \(O(1)\) |
| Append | Amortized \(O(1)\) under geometric growth | Amortized \(O(1)\) |
| Synchronization | Public methods are synchronized | Not synchronized by default |
| Capacity policy | Supports a fixed capacity increment | Uses its own growth policy |
| Legacy methods | Retains older names such as addElement | Focuses on collection API methods |
| Typical modern role | Legacy compatibility | General-purpose default list |
The core list operations are similar. The biggest practical distinction is Vector’s per-method synchronization. If locking is not needed, ArrayList avoids that overhead. If thread-safe access is required, a modern synchronized wrapper or a concurrency-oriented collection may better express the actual requirement.
9. Modern Alternatives for Thread-Safe Lists
There is no single best concurrent list for every workload. Two common choices serve different needs.
9.1 Collections.synchronizedList(...)
A synchronized wrapper can be created around a normal list:
The wrapper synchronizes individual method calls. It is useful when a program needs a synchronized list interface while keeping the underlying implementation replaceable.
Iteration requires extra care: the caller must synchronize on the returned wrapper while traversing it, following the API’s documented pattern. The wrapper does not make a series of separate calls atomic automatically; compound operations may still need an external synchronized block.
9.2 CopyOnWriteArrayList
CopyOnWriteArrayList uses a different strategy. A mutating operation creates a new internal array containing the updated elements, while readers can traverse a stable snapshot without taking the same kind of per-read lock.
This can work well when reads and iteration are frequent but updates are rare, such as event-listener collections or configuration data that changes infrequently. Writes are comparatively expensive because copying the array takes time and memory.
| Requirement | Potential fit |
|---|---|
| Ordinary list, no shared concurrent access | ArrayList |
| Synchronized method access around a regular list | Collections.synchronizedList(...) |
| Many reads and iterations, rare writes | CopyOnWriteArrayList |
| Existing API specifically requires Vector | Vector |
10. Where Vector Still Appears
Vector has not disappeared because Java places a high value on backward compatibility. It may still be encountered in:
- Older Java libraries whose public method signatures use Vector.
- Legacy applications built before the Collections Framework became standard.
- GUI and table-model APIs that accept Vector-based row data.
Stack, which extends Vector and inherits its synchronized methods.
When maintaining old code, avoid replacing Vector blindly. Check the API contracts and whether callers rely on its behavior. In new code without a compatibility constraint, a List implementation such as ArrayList is usually more appropriate.
11. Legacy Methods
Vector retains several methods that predate the modern collection interfaces.
| Legacy method | Modern-style equivalent |
|---|---|
elementAt(index) | get(index) |
addElement(value) | add(value) |
removeElementAt(index) | remove(index) |
removeElement(value) | remove(value) |
elements() | An Enumeration; modern code often prefers Iterator |
These methods remain available so older code continues to compile. For new development, using the standard List methods makes code easier to read alongside other collection implementations.
12. Common Mistakes
- Assuming synchronized methods make compound workflows atomic. Several calls can still interleave with another thread’s calls.
- Assuming Vector is always the best thread-safe list. Its coarse locking can be inefficient and may not match the workload.
- Confusing size with capacity. Spare capacity is not accessible as list elements.
- Choosing a tiny fixed capacity increment for a large collection. Frequent resizing can create significant copying overhead.
- Using legacy method names in new code without a reason. Prefer the common collection API.
- Assuming ArrayList and Vector have identical synchronization behavior. Their method-level concurrency guarantees differ.
- Using CopyOnWriteArrayList for frequent writes. Repeated array copies can be costly.
13. Best Practices
- Use
ArrayListfor ordinary list requirements when external synchronization is not needed. - Choose a concurrency strategy based on how often the collection is read, written, and iterated.
- Use
Collections.synchronizedListwhen a synchronized wrapper fits, and follow its iteration synchronization requirements. - Consider
CopyOnWriteArrayListfor read-heavy, rarely modified collections. - Protect multi-step operations as a whole when correctness depends on a sequence of calls.
- Keep Vector when a legacy API explicitly requires it, but avoid introducing it as the default for new code.
- Treat benchmark results as environment-dependent rather than assuming a fixed performance ratio.
14. Quick Revision
Vector<E>is a legacy resizable-array implementation ofList<E>.- It synchronizes its public methods.
- It supports indexed access and stores size separately from capacity.
- Its constructors can specify initial capacity and a capacity increment.
- Geometric growth supports amortized \(O(1)\) appends; small fixed increments may cause repeated copying.
- Individual synchronized calls do not make multi-call workflows atomic.
ArrayListis the normal default when synchronization is unnecessary.Collections.synchronizedListandCopyOnWriteArrayListaddress different concurrent-use patterns.- Vector remains relevant for backward compatibility and legacy APIs.
15. Conclusion
Vector is an important part of Java’s history and remains useful for compatibility with older APIs. It behaves much like a resizable-array list, but synchronizes public methods and retains older methods and capacity controls.
For new development, select a collection based on the actual requirements. Use ArrayList for ordinary list operations, a synchronized wrapper when its locking model fits, and CopyOnWriteArrayList when reads greatly outnumber writes. Keep Vector when compatibility requires it, and remember that per-method synchronization alone does not make a multi-step operation atomic.