Java Generics
Generics allow classes, interfaces, and methods to operate on typed parameters — meaning the type (like Integer, String, or any class) is specified at compile time, not at runtime. This makes code more flexible, type-safe, and reusable.
Without generics, a collection could hold any object, and a ClassCastException could occur at runtime. With generics, type errors are caught at compile time.
Why Use Generics?
- Type safety: Prevents storing wrong types in a collection.
- Eliminates casting: No need to manually cast objects when retrieving them.
- Code reuse: Write one class or method that works for multiple types.
Generics Without vs With
Without Generics (Unsafe)
import java.util.ArrayList;
ArrayList list = new ArrayList(); // raw type
list.add("Hello");
list.add(42); // different types – no compile-time error
String s = (String) list.get(1); // runtime ClassCastException!With Generics (Safe)
import java.util.ArrayList;
ArrayList<String> list = new ArrayList<>();
list.add("Hello");
// list.add(42); // compile-time error – only Strings allowed
String s = list.get(0); // no casting neededGeneric Classes
A class can be made generic by declaring a type parameter in angle brackets after the class name. The type parameter acts as a placeholder for the actual type provided when the class is used.
Example – A Generic Box
class Box<T> {
private T value;
Box(T value) {
this.value = value;
}
T getValue() {
return value;
}
void setValue(T value) {
this.value = value;
}
}
public class Main {
public static void main(String[] args) {
Box<String> stringBox = new Box<>("Java Generics");
Box<Integer> intBox = new Box<>(100);
Box<Double> doubleBox = new Box<>(3.14);
System.out.println("String box: " + stringBox.getValue());
System.out.println("Integer box: " + intBox.getValue());
System.out.println("Double box: " + doubleBox.getValue());
}
}Output:
String box: Java Generics
Integer box: 100
Double box: 3.14The same Box class works for any type. The type is determined when the object is created.
Multiple Type Parameters
A generic class can have more than one type parameter.
class Pair<K, V> {
K key;
V value;
Pair(K key, V value) {
this.key = key;
this.value = value;
}
void display() {
System.out.println("Key: " + key + " | Value: " + value);
}
}
public class Main {
public static void main(String[] args) {
Pair<String, Integer> student = new Pair<>("Alice", 95);
Pair<Integer, String> code = new Pair<>(404, "Not Found");
student.display();
code.display();
}
}Output:
Key: Alice | Value: 95
Key: 404 | Value: Not FoundGeneric Methods
A single method can be made generic independently of its class. The type parameter is declared before the return type.
public class GenericMethods {
static <T> void printArray(T[] array) {
for (T item : array) {
System.out.print(item + " ");
}
System.out.println();
}
public static void main(String[] args) {
Integer[] ints = {1, 2, 3, 4, 5};
String[] words = {"Hello", "Java", "World"};
Double[] decimals = {1.1, 2.2, 3.3};
printArray(ints);
printArray(words);
printArray(decimals);
}
}Output:
1 2 3 4 5
Hello Java World
1.1 2.2 3.3 Bounded Type Parameters
Sometimes a type parameter should be restricted to a certain class or its subclasses. This is done using the extends keyword (an upper bound).
static <T extends Number> double sum(T[] array) {
double total = 0;
for (T item : array) {
total += item.doubleValue();
}
return total;
}
public static void main(String[] args) {
Integer[] ints = {10, 20, 30};
Double[] doubles = {1.5, 2.5, 3.5};
System.out.println("Sum of ints: " + sum(ints)); // 60.0
System.out.println("Sum of doubles: " + sum(doubles)); // 7.5
// sum(new String[]{"a","b"}); // compile error – String is not a Number
}Wildcard – The ? Symbol
The wildcard ? represents an unknown type. It is used in method parameters when the exact type is unknown or flexible.
Unbounded Wildcard
static void printList(ArrayList<?> list) {
for (Object item : list) {
System.out.print(item + " ");
}
System.out.println();
}Upper-Bounded Wildcard (? extends Type)
Accepts any type that is a subtype of the specified type.
static double totalArea(ArrayList<? extends Shape> shapes) {
double total = 0;
for (Shape s : shapes) {
total += s.area();
}
return total;
}Lower-Bounded Wildcard (? super Type)
Accepts any type that is a supertype of the specified type.
static void addNumbers(ArrayList<? super Integer> list) {
list.add(10);
list.add(20);
}Generics with Interfaces
interface Comparable<T> {
int compareTo(T other);
}
class Temperature implements Comparable<Temperature> {
double value;
Temperature(double value) { this.value = value; }
@Override
public int compareTo(Temperature other) {
return Double.compare(this.value, other.value);
}
}Common Generic Type Parameter Names
| Symbol | Convention |
|---|---|
| T | Type (general purpose) |
| E | Element (used in collections) |
| K | Key (used in maps) |
| V | Value (used in maps) |
| N | Number |
Summary
- Generics allow classes, interfaces, and methods to work with any data type safely.
- Type parameters are declared using angle brackets:
<T>,<K, V>. - Generics provide compile-time type checking, eliminating runtime
ClassCastException. - Bounded type parameters use
extendsto restrict the accepted types. - The wildcard
?represents an unknown type and is used in flexible method signatures. - All Java collection classes (ArrayList, HashMap, etc.) use generics internally.
