Java Notes
Complete guide to Java Stack class — LIFO principle, internal working, legacy status, why ArrayDeque is preferred, methods, use cases, and interview questions.
What is a Stack?
A Stack is a data structure that follows the LIFO (Last In, First Out) principle. The last element added is the first one to be removed — like a stack of plates.
| push(C) | adds to top |
| pop() | removes from top (returns C) |
| peek() | looks at top without removing (returns C) |
Internal Working
Since Stack extends Vector, it uses a dynamic array internally:
// Vector's internal structure (inherited by Stack)
protected Object[] elementData; // The array buffer
protected int elementCount; // Number of valid elements
protected int capacityIncrement; // How much to grow (0 = double)How push() Works
How pop() Works
public synchronized E pop() {
E obj;
int len = size();
obj = peek(); // Get top element
removeElementAt(len - 1); // Remove last element
return obj;
}How peek() Works
public synchronized E peek() {
int len = size();
if (len == 0) throw new EmptyStackException();
return elementAt(len - 1); // Return last element without removing
}Stack Methods
Push: [10, 20, 30, 40, 50] Peek: 50 Pop: 50 After pop: [10, 20, 30, 40] Search 20: 3 Search 99: -1 Empty: false Size: 4
Time Complexity
| Operation | Time Complexity | Notes |
|---|---|---|
| ----------- | :-: | --- |
push(E e) | O(1) amortized | May trigger array resize O(n) rarely |
pop() | O(1) | Remove from end of array |
peek() | O(1) | Access last element |
empty() | O(1) | Check size |
search(Object o) | O(n) | Linear search from top |
Inherited get(int) | O(1) | Random access (breaks LIFO!) |
Why Stack is Legacy — Problems
Problem 1: Extends Vector (Tight Coupling)
Stack extends Vector, inheriting ALL list methods that violate LIFO:
Stack<String> stack = new Stack<>();
stack.push("A");
stack.push("B");
stack.push("C");
// These shouldn't be possible for a "stack" but they compile fine:
stack.add(0, "Inserted at bottom!"); // Breaks LIFO!
stack.get(1); // Random access!
stack.remove(0); // Remove from middle!
stack.set(1, "Modified!"); // Modify anywhere!Problem 2: Synchronized (Performance Cost)
Every method in Stack is synchronized (inherited from Vector), even in single-threaded code:
// Even push() acquires a lock — unnecessary overhead!
public synchronized void addElement(E obj) { ... }Problem 3: Inheritance vs Composition
Stack uses inheritance (IS-A Vector) instead of composition (HAS-A array). This violates encapsulation.
The Modern Alternative: ArrayDeque
Java's documentation explicitly states:
*"A more complete and consistent set of LIFO stack operations is provided by the Deque interface and its implementations, which should be used in preference to this class."*
// PREFERRED — Use ArrayDeque as a stack
Deque<String> stack = new ArrayDeque<>();
stack.push("A"); // addFirst
stack.push("B"); // addFirst
stack.push("C"); // addFirst
System.out.println(stack.peek()); // C (top)
System.out.println(stack.pop()); // C (removes top)
System.out.println(stack.pop()); // BComparison: Stack vs ArrayDeque
| Feature | Stack | ArrayDeque |
|---|---|---|
| Thread-safe | Yes (synchronized) | No (faster single-threaded) |
| Inheritance | Extends Vector (bloated) | Implements Deque (clean) |
| Random access | Yes (breaks abstraction) | No (proper encapsulation) |
| null elements | Allowed | NOT allowed |
| Performance | Slower (synchronization overhead) | Faster (no locking) |
| Memory | May waste space (Vector growth) | Efficient circular array |
| Recommended | No (legacy) | Yes (modern) |
Stack Use Cases
1. Expression Evaluation
2. Balanced Parentheses
3. Undo/Redo Mechanism
public class TextEditor {
private StringBuilder text = new StringBuilder();
private Deque<String> undoStack = new ArrayDeque<>();
private Deque<String> redoStack = new ArrayDeque<>();
public void type(String s) {
undoStack.push(text.toString());
redoStack.clear();
text.append(s);
}
public void undo() {
if (!undoStack.isEmpty()) {
redoStack.push(text.toString());
text = new StringBuilder(undoStack.pop());
}
}
public void redo() {
if (!redoStack.isEmpty()) {
undoStack.push(text.toString());
text = new StringBuilder(redoStack.pop());
}
}
public String getText() { return text.toString(); }
}4. Browser History
public class BrowserHistory {
private Deque<String> backStack = new ArrayDeque<>();
private Deque<String> forwardStack = new ArrayDeque<>();
private String currentPage;
public void visit(String url) {
if (currentPage != null) backStack.push(currentPage);
currentPage = url;
forwardStack.clear();
}
public String back() {
if (backStack.isEmpty()) return currentPage;
forwardStack.push(currentPage);
currentPage = backStack.pop();
return currentPage;
}
public String forward() {
if (forwardStack.isEmpty()) return currentPage;
backStack.push(currentPage);
currentPage = forwardStack.pop();
return currentPage;
}
}Thread-Safe Stack Alternatives
// Option 1: ConcurrentLinkedDeque (non-blocking)
Deque<String> concurrentStack = new ConcurrentLinkedDeque<>();
concurrentStack.push("item");
concurrentStack.pop();
// Option 2: Synchronized wrapper
Deque<String> syncStack = Collections.synchronizedDeque(new ArrayDeque<>());
// Note: synchronizedDeque is not available — use synchronizedList or explicit lock
// Option 3: Explicit locking
private final Lock lock = new ReentrantLock();
private final Deque<String> stack = new ArrayDeque<>();
public void push(String item) {
lock.lock();
try { stack.push(item); }
finally { lock.unlock(); }
}Common Mistakes
1. Using Stack When ArrayDeque is Better
// LEGACY — don't use
Stack<Integer> stack = new Stack<>();
// MODERN — use this
Deque<Integer> stack = new ArrayDeque<>();2. Not Checking for Empty Before pop/peek
// WRONG — may throw EmptyStackException or NoSuchElementException
String top = stack.pop();
// CORRECT
if (!stack.isEmpty()) {
String top = stack.pop();
}
// OR use poll()/peek() which return null instead of throwing
String top = stack.poll(); // returns null if empty3. Using Stack's List Methods
Stack<String> stack = new Stack<>();
stack.push("A");
stack.push("B");
stack.add(0, "C"); // DON'T! This inserts at the bottom — breaks LIFOInterview Questions
Q1: Why is the Stack class considered legacy?
A: Because it extends Vector (inheriting unnecessary synchronized methods and list operations that break the LIFO abstraction). ArrayDeque is the recommended replacement.
Q2: What is the difference between Stack and ArrayDeque for stack operations?
A: Stack is synchronized (slower), extends Vector (exposes non-stack methods), and allows null. ArrayDeque is faster (no synchronization), properly encapsulated (Deque interface), and disallows null.
Q3: How would you implement a thread-safe stack?
A: Use ConcurrentLinkedDeque, or wrap an ArrayDeque with explicit ReentrantLock, or use the legacy Stack class (not recommended for new code).
Q4: What exception does pop() throw on an empty stack?
A: Stack.pop() throws EmptyStackException. ArrayDeque.pop() throws NoSuchElementException. Use poll() for null-return behavior.
Q5: Can you implement a stack using two queues?
A: Yes. On push, add to queue and rotate all previous elements behind it. Pop and peek then operate on the front. Or keep two queues and swap on pop.
Q6: What is the time complexity of the search() method in Stack?
A: O(n) — it performs a linear scan from the top of the stack. Returns a 1-based position (top = 1) or -1 if not found.
Summary
| Aspect | Stack (Legacy) | ArrayDeque (Modern) |
|---|---|---|
| Use for new code | ❌ No | ✅ Yes |
| Thread-safe | Yes (but costly) | No (wrap if needed) |
| API cleanliness | Poor (inherits Vector) | Clean (Deque only) |
| Performance | Slower | Faster |
| Null support | Yes | No |
| Key operations | push, pop, peek, search | push, pop, peek |
Bottom line: Always use Deque<E> stack = new ArrayDeque<>() for stack operations in new code.
Q8: Why is Stack class considered legacy/deprecated in practice?
A: Stack extends Vector, inheriting all its synchronized overhead even in single-threaded scenarios. It also inherits List methods like get(index) which violate LIFO semantics. Java docs recommend ArrayDeque for stack operations — it's faster and properly restricts access to top-of-stack operations only.
Q9: What is the difference between Stack.push() and Vector.add()?
A: push() adds to the top (end) of the stack and returns the element pushed. add() (inherited from Vector/List) adds to the end and returns boolean. Functionally similar, but push() is the proper stack operation and makes code intent clearer.
Q10: How does Stack.search() work?
A: search(Object) returns the 1-based position from the top of the stack. The top element has position 1, the one below it has position 2, etc. Returns -1 if the element is not found. It uses equals() for comparison, searching from top to bottom.
Q11: How do you implement a thread-safe stack without using the Stack class?
A: Use Collections.synchronizedList(new LinkedList<>()) with addFirst/removeFirst, or better: use ConcurrentLinkedDeque from java.util.concurrent which provides lock-free thread-safe stack operations (push/pop/peek).
Q12: What are real-world use cases of Stack data structure?
A: (1) Undo/Redo operations in editors, (2) Browser back/forward navigation, (3) Expression evaluation and parsing (postfix, infix conversion), (4) Function call stack (recursion), (5) Balanced parentheses checking, (6) Depth-First Search (DFS) in graphs.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Stack in Java — Collections Framework.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Java Master Course topic.
Search Terms
java-master-course, java master course, java, master, course, collections, framework, list
Related Java Master Course Topics