Before Java 5 introduced generics, collections and data structures relied on raw Object references. While this allowed containers to hold any reference type, it deferred type checks to runtime, creating recurring risks of ClassCastException bugs.
The Pitfall of Storing Raw Objects
Consider a traditional stack built on Object[]:
ObjectStack stack = new ObjectStack();
stack.push("Active Connection");
stack.push(1024); // Accidental mismatch accepted without compiler warnings
String connection = (String) stack.pop(); // Throws ClassCastException at runtime
Whenever an element is popped, the developer must manually cast it. If an invalid type was inserted earlier, the program fails at runtime rather than during compilation.
Implementing a Generic Stack (<T>)
Generics allow a class to operate over types specified by the caller at initialization. The compiler enforces checks automatically, eliminating manual casting.
Here is a dynamic, bounded-growth generic stack:
import java.util.EmptyStackException;
public class GenericStackDemo<T> {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_CAPACITY = 10;
public GenericStackDemo() {
elements = new Object[DEFAULT_CAPACITY];
}
public void push(T item) {
if (size == elements.length) {
resize();
}
elements[size++] = item;
}
@SuppressWarnings("unchecked")
public T pop() {
if (isEmpty()) {
throw new EmptyStackException();
}
T item = (T) elements[--size];
elements[size] = null; // Prevent memory leak by clearing obsolete reference
return item;
}
public boolean isEmpty() {
return size == 0;
}
private void resize() {
Object[] newElements = new Object[elements.length * 2];
System.arraycopy(elements, 0, newElements, 0, elements.length);
elements = newElements;
}
}
Key Architectural Takeaways
-
Type Erasure Workaround: Direct generic array creation (e.g.,
new T[DEFAULT_CAPACITY]) causes a compile-time error because Java erases type parameters at runtime. Backing the store withObject[]and isolating the cast insidepop()safely circumvents this constraint. -
Eliminating Obsolete References: Clearing the vacated slot (
elements[size] = null) ensures the Garbage Collector can reclaim popped objects immediately rather than holding them in active memory. - Compile-Time Enforcement:
GenericStackDemo<String> stringStack = new GenericStackDemo<>();
stringStack.push("Dev.to");
// stringStack.push(101); // Caught by the compiler immediately
String item = stringStack.pop(); // Clean extraction with no explicit cast
Companion Code
Full runnable code is available on GitHub.
Top comments (0)