Generics allow Java classes, interfaces, and methods to operate on a specific data type without losing type safety.
Before generics (Java 1.4), collections stored Object, requiring manual casting and risking runtime errors. Generics provide compile-time type checking and eliminate casting.
Example without generics:
ArrayList list = new ArrayList();
list.add("Java");
String s = (String) list.get(0); // manual castWith generics:
ArrayList<String> list = new ArrayList<>();
String s = list.get(0); // no cast neededPrevents inserting wrong types.
ArrayList<Integer> list = new ArrayList<>();
// list.add("Hello"); // compile errorOne class works for multiple data types.
No need for explicit casting.
A class can be made generic using type parameters.
class Box<T> {
private T value;
public void set(T value) { this.value = value; }
public T get() { return value; }
}Usage:
Box<Integer> b1 = new Box<>();
b1.set(10);
Box<String> b2 = new Box<>();
b2.set("Hello");Methods can declare their own type parameters.
public <T> void print(T value) {
System.out.println(value);
}Usage:
print(10);
print("Java");
print(5.6);class Pair<K, V> {
K key;
V value;
}Usage:
Pair<Integer, String> p = new Pair<>();Used to restrict type parameters.
public <T extends Number> void printNumber(T n) {
System.out.println(n);
}Now T can be: Integer, Double, Float, etc.
public void addNumbers(List<? super Integer> list) {
list.add(10); // allowed
}Means: list can be List<Integer>, List<Object>, but not List<Double>.
A wildcard represents an unknown type.
void printList(List<?> list) {
for (Object o : list)
System.out.println(o);
}Can accept any type of list.
List<? extends Number>Means the list contains Number or subclasses. Used for reading values.
List<? super Integer>Means list contains Integer or its superclasses. Used for writing values.
| Wildcard | Meaning | Best For |
|---|---|---|
<?> |
unknown type | reading only |
<? extends T> |
T or child | reading |
<? super T> |
T or parent | writing |
Generics exist only during compile time. At runtime, Java removes generic type information — this is called type erasure.
Example:
ArrayList<Integer> a = new ArrayList<>();
ArrayList<String> b = new ArrayList<>();At runtime, both are treated as ArrayList.
Because of type erasure:
- Cannot create generic arrays:
T[] arr = new T[5];❌ - Cannot use primitives as type parameters:
ArrayList<int>❌ - Cannot check type with
instanceof:if (obj instanceof List<String>)❌
T obj = new T(); // ❌ not allowedList<String>[] arr = new List<String>[10]; // ❌List<int> list; // ❌Use wrapper classes:
List<Integer> list;interface Container<T> {
T process(T item);
}class Demo {
<T> Demo(T value) {
System.out.println(value);
}
}| Class | Generic Form |
|---|---|
| ArrayList | ArrayList<E> |
| HashMap | HashMap<K, V> |
| HashSet | HashSet<E> |
| TreeMap | TreeMap<K, V> |
Example:
HashMap<Integer, String> map = new HashMap<>();Box<String> box = new Box<>();Comparator<String> comp = Comparator.naturalOrder();Stack<Employee> stack = new Stack<>();A way to write type-safe, reusable code using type parameters.
extends→ upper bound (read-only)super→ lower bound (write allowed)
Due to type erasure for backward compatibility.
Because generics work only with objects, not primitives.
Yes.
-
Generics allow type-safe programming with compile-time checking.
-
Eliminates the need for casting.
-
Wildcards (
?, extends, super) allow flexible type usage. -
Type erasure removes generic info at runtime.
-
Collections heavily rely on generics.
-
Helps build reusable classes and methods.