[!NOTE] Generics were added in Java 5 to provide compile-time type checking and remove the risk of
ClassCastExceptionthat plagued early developers. Understanding Generics is what separates junior developers from mid-level engineers.
Life before Generics
Before Generics, a standard Java ArrayList simply stored raw Object references. You could throw anything into it:
ArrayList myCart = new ArrayList(); // No type declared!
myCart.add("Apple"); // String
myCart.add(450); // Integer
myCart.add(new Car()); // Car Object
// To retrieve the Apple, you had to manually "cast" it, praying it was actually a String.
String fruit = (String) myCart.get(0);
// If you accidentally grabbed index 1, your entire app crashed violently at Runtime!
String broken = (String) myCart.get(1); // ClassCastException: Cannot cast Integer to String
Life with Generics
Generics fix this by forcing you to specify the Type Parameter inside angle brackets <> .
// The compiler now strictly enforces that ONLY Strings enter this list!
ArrayList<String> myCart = new ArrayList<>();
myCart.add("Apple");
// myCart.add(450); // COMPILER ERROR! Refuses to run!
String fruit = myCart.get(0); // No manual casting required!
Creating Your Own Generic Class
You can use Generics in your own classes to create ultra-reusable architecture. Instead of hardcoding a type like String, you use a placeholder letter (conventionally T for Type).
// 1. We declare 'T' as a flexible placeholder when we write the blueprint.
public class Box<T> {
private T contents;
public void setContents(T item) {
this.contents = item;
}
public T getContents() {
return this.contents;
}
}
public class Main {
public static void main(String[] args) {
// 2. We physically lock in the Type specifically when we instantiate the Box!
Box<Integer> integerBox = new Box<Integer>();
integerBox.setContents(42);
Box<String> stringBox = new Box<String>();
stringBox.setContents("Secret Document");
}
}
[!CAUTION] Type Erasure: You cannot use primitive types like
intinside angle brackets because Generics actively erase their type boundaries at runtime (a concept called Type Erasure) to maintain backwards compatibility with 1990s Java code. You MUST use wrapper classes like<Integer>!