Nearby lessons

81 of 125

Java - Generics

📌 What You Will Learn
  • The problem generics solve
  • Generic classes and generic methods
  • Bounded type parameters
  • Wildcards: ?, ? extends T, ? super T
  • Type erasure — how generics work inside

Generics is a core concept of the Java language. This lesson explains The Problem Generics Solve, Generic Class and Generic Method with complete, runnable code examples, clear step-by-step explanations, and common mistakes to avoid with exam-style MCQs at the end.

The Problem Generics Solve

Before Java 5, a collection stored any kind of object. That meant you could accidentally add a String into a list of numbers, and the mistake would be discovered only at run time (crash), not at compile time.

Generics (Java 5+) fix this by letting you say clearly: this list can hold only Integers. The compiler then checks at compile time and stops mistakes early.

In simple words: Generics move the type check from run time to compile time — a List<Integer> simply refuses a String, so the mistake shows up as an error in the editor instead of a crash later.
Example01
JCode Cell
1// BEFORE generics - dangerous
2List numbers = new ArrayList();
3numbers.add(10);
4numbers.add("hello"); // mistake! accepted silently
5int x = (int) numbers.get(1); // crash at run time (ClassCastException)

The Problem Generics Solve

Benefits: type safety (mistakes found at compile time), no casting (cleaner code), and self-documenting code (you can read what the list holds).

Example02
JCode Cell
1// AFTER generics - safe
2List<Integer> numbers = new ArrayList<>();
3numbers.add(10);
4// numbers.add("hello"); // COMPILE TIME ERROR - good!
5int x = numbers.get(0); // no cast needed
6System.out.println(x);
Output
10

Generic Class

A generic class uses type parameters written in angle brackets <T>. T is a placeholder for the real type, decided when we use the class.

In simple words: `<T>` is just a placeholder: one generic `Box<T>` class becomes many typed versions — Box<String>, Box<Integer>, Box<Student> — depending on the type you put in the angle brackets.

One class Box, many types — Box<String>, Box<Integer>, Box<Student>. The angle brackets < > are called the diamond operator (Java 7 lets us write new Box<>() without repeating the type).

Example03
JCode Cell
1class Box<T> { // T = type parameter
2 private T item;
3 
4 void set(T item) { this.item = item; }
5 T get() { return item; }
6}
7 
8class Demo {
9 public static void main(String[] args) {
10 Box<String> box1 = new Box<>();
11 box1.set("Books");
12 System.out.println(box1.get());
13 
14 Box<Integer> box2 = new Box<>();
15 box2.set(100);
16 System.out.println(box2.get());
17 }
18}
Output
Books 100

Generic Method

A generic method has its own type parameter before the return type:

Example04
JCode Cell
1class Util {
2 // generic method: T is decided when we call it
3 static <T> void printAll(T[] items) {
4 for (T item : items) {
5 System.out.print(item + " ");
6 }
7 System.out.println();
8 }
9}
10 
11class Demo {
12 public static void main(String[] args) {
13 String[] names = {"Rahul", "Priya"};
14 Integer[] nums = {1, 2, 3};
15 Util.printAll(names); // works with String
16 Util.printAll(nums); // and with Integer - same method
17 }
18}
Output
Rahul Priya 1 2 3

Bounded Type Parameters

Sometimes we want T to be restricted to a specific family — like only numbers so we can add them. We use extends to set an upper bound.

This gives us access to Number's methods (like doubleValue()), which plain T would not have.

Example05
JCode Cell
1class NumberBox<T extends Number> { // T must be a subclass of Number
2 T value;
3 double doubleValue() {
4 return value.doubleValue();
5 }
6}
7 
8// NumberBox<String> b = new NumberBox<>(); // ERROR! String is not a Number

Wildcards — ? , ? extends T , ? super T

Wildcards make generic methods more flexible. The ? means unknown type.

SyntaxMeaningExample use
?Any typevoid show(List<?> list) — accepts a list of anything
? extends TT or its children (upper bound)void sum(List<? extends Number> list) — accepts Integer, Double...
? super TT or its parents (lower bound)void add(List<? super Integer> list) — accepts Integer or Object lists
In simple words: `?` means any type, `? extends T` means T or its children, and `? super T` means T or its parents — wildcards let one method accept a whole family of types.
Trainer's Note: Easy memory trick: PECS — Producer Extends, Consumer Super. If a method produces (reads) values from a collection, use ? extends T. If it consumes (writes) values into it, use ? super T.
Example06
JCode Cell
1import java.util.*;
2 
3class WildcardDemo {
4 // accepts a list of ANY Number type (Integer, Double, ...)
5 static double sum(List<? extends Number> list) {
6 double total = 0;
7 for (Number n : list) total += n.doubleValue();
8 return total;
9 }
10 
11 public static void main(String[] args) {
12 List<Integer> ints = Arrays.asList(1, 2, 3);
13 List<Double> dbls = Arrays.asList(1.5, 2.5);
14 System.out.println("Sum of ints: " + sum(ints));
15 System.out.println("Sum of dbls: " + sum(dbls));
16 }
17}
Output
Sum of ints: 6.0 Sum of dbls: 4.0

Type Erasure — How Generics Really Work

Here is a secret: generics exist only at compile time. At run time, Java erases all type information (this is called type erasure). The compiled .class file works with plain Object, and the compiler adds the casts for you.

In simple words: Type erasure means generics are a compile-time-only illusion — the compiler checks your types, then wipes them away and inserts the casts, so at run time List<String> is just a plain List.

That is why list instanceof List<String> is not allowed at run time — the <String> part simply does not exist then. The generics system protects you at compile time, then quietly steps aside at run time.

Example07
JCode Cell
1// What you write:
2List<String> list = new ArrayList<>();
3String s = list.get(0);
4 
5// What the compiler makes (approximately):
6List list = new ArrayList(); // type erased
7String s = (String) list.get(0); // cast added automatically

Common Generic Types You Already Use

Example08
JCode Cell
1ArrayList<String> -> list of Strings
2HashMap<String, Integer> -> key=String, value=Integer
3Comparator<Student> -> custom sorter for Student
4Optional<String> -> a box that may or may not hold a String

Why Generics with Collections — Question and Answer

The classic material links generics tightly to collections. The two reasons:

  • Type safety — without generics, a collection accepts anything, and mistakes are found only at run time. With generics, ArrayList<String> refuses a number at compile time.
  • No casting — when you read from a collection, generics remove the manual cast: String s = list.get(0) instead of String s = (String) list.get(0).

That is why modern Java code always writes List<String>, Map<String, Integer> etc. — generics make collections safe and clean.

Example09
JCode Cell
1// Without generics - cast needed and run-time risk
2List old = new ArrayList();
3old.add("abc");
4String s = (String) old.get(0); // cast required
5 
6// With generics - no cast, compile-time safe (Java 5+)
7List<String> modern = new ArrayList<>();
8modern.add("abc");
9String s2 = modern.get(0); // no cast needed
📝 Key Takeaways
  • Generics give compile-time type safety and remove manual casting.
  • Generic class: class Box<T>; generic method: static <T> void m(...).
  • Bounded types: <T extends Number> restricts T.
  • Wildcards: ? = any, ? extends T = T or children, ? super T = T or parents.
  • PECS: producers use extends, consumers use super.
  • Type erasure: generics are compile-time only; at run time they are gone.

🧠 Test Your Knowledge

10 Questions
Progress: 0 / 10