DEV Community

DHRUV AHIR
DHRUV AHIR

Posted on

Type Safety Without Explicit Casting: Building a Custom Generic Stack in Java

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
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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 with Object[] and isolating the cast inside pop() 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
Enter fullscreen mode Exit fullscreen mode

Companion Code

Full runnable code is available on GitHub.

Top comments (0)