Create a Generic Stack Class in Java
Problem
A generic class declares a type parameter such as T, which callers fill in with a real type; the compiler then checks every use, so the wrong type can't be added and no casts are needed.
Implement a stack that works for any element type, and use it for both browser pages (strings) and numbers.
Java Program
import java.util.ArrayList;
import java.util.EmptyStackException;
import java.util.List;
public class GenericStack {
static class Stack<T> { // T is filled in by the caller: Stack<String>, Stack<Integer>, ...
private final List<T> items = new ArrayList<>();
void push(T item) {
items.add(item);
}
T pop() {
if (items.isEmpty()) throw new EmptyStackException();
return items.remove(items.size() - 1);
}
T peek() {
if (items.isEmpty()) throw new EmptyStackException();
return items.get(items.size() - 1);
}
boolean isEmpty() {
return items.isEmpty();
}
}
public static void main(String[] args) {
Stack<String> pages = new Stack<>();
pages.push("home");
pages.push("products");
pages.push("cart");
System.out.println("Top: " + pages.peek());
System.out.println("Back from: " + pages.pop());
System.out.println("Now on: " + pages.peek());
Stack<Integer> numbers = new Stack<>(); // same class, different type, no casts needed
numbers.push(42);
int value = numbers.pop();
System.out.println("Popped number: " + value + ", empty: " + numbers.isEmpty());
}
}Output
Core Logic
Declaring the class as Stack<T> lets one implementation serve every element type while the compiler enforces the type chosen at each use.
- 1
Tappears in the fieldList<T>and in the method signaturespush(T),pop()andpeek(). - 2
Stack<String>accepts only strings, so pushing a number there is a compile-time error. - 3
popandpeekthrowEmptyStackExceptioninstead of failing with an index error. - 4
Stack<Integer>reuses the same class;int value = numbers.pop()unboxes without a cast.
pop() returns cart and peek() then shows products.Key Point: Generics are checked at compile time and erased at run time — inside the class, new T() or new T[10] isn't allowed, because the JVM doesn't know what T is.
Key Concepts
Approach 2: Generic Method with a Bounded Type
import java.util.Arrays;
import java.util.List;
public class GenericBoundedMethod {
// <T extends Comparable<T>>: T can be any type whose values can be compared with each other
static <T extends Comparable<T>> T max(List<T> items) {
T best = items.get(0);
for (T item : items) {
if (item.compareTo(best) > 0) best = item;
}
return best;
}
public static void main(String[] args) {
System.out.println(max(Arrays.asList(4, 19, 7, 11)));
System.out.println(max(Arrays.asList("pear", "apple", "plum")));
System.out.println(max(Arrays.asList(2.5, 0.75, 1.25)));
}
}
Output
Core Logic
A generic method declares its own type parameter, and a bound such as T extends Comparable<T> lets it call compareTo on any type that supports it.
- 1
<T extends Comparable<T>>before the return type declares the method's type parameter and its bound. - 2Because every
TisComparable,item.compareTo(best)compiles. - 3The same method finds the maximum of integers, strings and doubles.
plum is the maximum because it comes last alphabetically.Key Point: Without the bound, the compiler would only know T is an Object, and compareTo wouldn't compile.
Key Concepts
Interview Questions
What is type erasure?
The compiler checks generic types and then removes them, so at run time a Stack<String> and a Stack<Integer> are the same class. That's why you can't write new T() or check obj instanceof Stack<String>.
Why can't a generic class use a primitive type like Stack<int>?
Type parameters must be reference types. Use the wrapper class, Stack<Integer>, and autoboxing converts between int and Integer automatically.