Legacy collections in Java
Vector, Stack, Hashtable — why ArrayDeque and HashMap replaced them.
Fossils from Java 1.0
**Vector, Stack, Hashtable and Enumeration** predate the Collections Framework (Java 1.2). They still work, but each has a modern replacement.
// Vector → ArrayList
// Stack → ArrayDeque
// Hashtable → HashMap / ConcurrentHashMap
// Enumeration → IteratorSynchronized ≠ thread-safe
Every method is synchronized: each call takes a lock. That's slower in single-threaded code, and it still doesn't make sequences of calls atomic. Between containsKey and put, another thread can sneak in.
// two calls, two separate locks:
if (!table.containsKey(k)) {
table.put(k, v); // race!
}Your turn
What does this print?
Stack<String> s = new Stack<>();
s.push("x");
s.push("y");
s.push("z");
System.out.println(s + " " + s.get(0));[z, y, x] z[x, y, z] x[x, y, z] z
Show the answer
Stack extends Vector, so it stores elements bottom first: toString prints [x, y, z]. And get(0) returns the bottom element, an index operation a real stack shouldn't even allow.
Stack is a Vector in disguise
Because **Stack extends Vector**, it inherits every List method: get(i), add(i, x), remove(i). Anyone can reach into the middle of your "stack". A Deque only exposes the ends.
Stack<Integer> old = new Stack<>();
old.add(0, 99); // sneaks in at the bottom!
Deque<Integer> s = new ArrayDeque<>();
s.push(1); // only the endsNull in a Hashtable
What happens?
Map<String, String> t = new Hashtable<>();
t.put("k", null);
System.out.println(t);Prints {k=null}Prints {}Throws NullPointerException
Show the answer
Hashtable rejects null keys AND null values with NullPointerException. HashMap allows one null key and any number of null values.
Shared counters across threads
Hashtable<String, Integer> hits =
new Hashtable<>();
Integer n = hits.get(page);
hits.put(page, n == null ? 1 : n + 1);Two separate locked calls: two threads can read the same n and lose an update.
var hits =
new ConcurrentHashMap<String, Integer>();
hits.merge(page, 1, Integer::sum);merge does the read-modify-write atomically per key, without one big lock.
Legacy in the wild
You'll meet these classes in older enterprise code and some old APIs (Swing still uses Vector). When modernising, swap Vector → ArrayList, Stack → ArrayDeque, Hashtable → HashMap, or ConcurrentHashMap when threads share it, and run the tests: watch for code that relied on nulls being rejected.
Key takeaways
- Vector → ArrayList
- Stack → ArrayDeque (push/pop/peek)
- Hashtable → HashMap, or ConcurrentHashMap for threads
- Hashtable rejects null keys and values
Stack's own Javadoc recommends against it: it says the Deque interface provides a more complete and consistent set of LIFO stack operations, and suggests ArrayDeque.
Practice questions
What does this print?
Stack<String> s = new Stack<>();
s.push("a");
s.push("b");
s.push("c");
System.out.println(s + " " + s.get(0));- [c, b, a] c
- [a, b, c] a
- [a, b, c] c
- [c, b, a] a
Check your answer
[a, b, c] a. Stack extends Vector, so it stores elements bottom-first and toString prints [a, b, c]. get(0) returns the bottom element — an index operation a real stack shouldn't even allow.
Why are Vector and Hashtable discouraged in new code?
- They were removed in Java 17
- Every method is synchronized — slower in single-threaded code, yet compound actions like check-then-put still aren't atomic
- They can only store Strings
- They don't implement any collection interfaces
Check your answer
Every method is synchronized — slower in single-threaded code, yet compound actions like check-then-put still aren't atomic. Locking each individual call adds overhead but doesn't protect sequences of calls. Use unsynchronized collections, or purpose-built concurrent ones like ConcurrentHashMap.