Java Generics
Generics is a powerful feature introduced in Java SE 5.0 that allows us to use type parameters when defining classes, interfaces, and methods. Using generics enables writing more flexible, safer, and clearer code.
Why Do We Need Generics?
Before generics were introduced, Java's collection classes (like ArrayList) could only store objects of type Object. This approach had two main problems:
- Type unsafe: You could add any type of object to an
ArrayList, but errors couldn't be detected at compile time.java// Without generics List list = new ArrayList(); list.add("Hello"); list.add(123); // No compile-time error - Required explicit type casting: When retrieving elements from a collection, you had to manually cast them to the expected type, which was cumbersome and could throw
ClassCastExceptionat runtime.javaString first = (String) list.get(0); String second = (String) list.get(1); // Runtime error! ClassCastException
Generics perfectly solved these problems by moving type checking from runtime to compile time.
// Using generics
List<String> stringList = new ArrayList<>();
stringList.add("Hello");
// stringList.add(123); // Compile error! Type mismatch
String first = stringList.get(0); // No casting neededGeneric Classes
We can define our own generic classes. Type parameters (usually represented by single uppercase letters, such as T for Type, E for Element, K for Key, V for Value) are declared in angle brackets <> after the class name.
// Define a generic class Box
public class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
// Using the generic class
public class Main {
public static void main(String[] args) {
// Create a Box that stores String
Box<String> stringBox = new Box<>();
stringBox.setContent("This is a string");
System.out.println(stringBox.getContent());
// Create a Box that stores Integer
Box<Integer> integerBox = new Box<>();
integerBox.setContent(100);
System.out.println(integerBox.getContent());
}
}Generic Methods
Besides generic classes, we can also define generic methods. Generic methods have their own type parameters, which are declared before the method return type.
public class GenericMethodExample {
// Define a generic method
public static <E> void printArray(E[] inputArray) {
for (E element : inputArray) {
System.out.printf("%s ", element);
}
System.out.println();
}
public static void main(String[] args) {
Integer[] intArray = {1, 2, 3, 4, 5};
String[] stringArray = {"Hello", "World"};
System.out.println("Integer array:");
printArray(intArray); // Compiler infers E is Integer
System.out.println("String array:");
printArray(stringArray); // Compiler infers E is String
}
}Bounded Type Parameters
Sometimes we want to restrict the types that can be used as type parameters. For example, a method might only accept instances of Number or its subclasses. This can be achieved through bounded type parameters.
<T extends UpperBound>:Tmust be of typeUpperBoundor its subtype.
public class BoundedTypeExample {
// T must be Number or its subclass (like Integer, Double)
public static <T extends Number> double getDoubleValue(T number) {
return number.doubleValue();
}
public static void main(String[] args) {
System.out.println(getDoubleValue(10)); // Integer
System.out.println(getDoubleValue(3.14)); // Double
// getDoubleValue("text"); // Compile error! String is not a subclass of Number
}
}Wildcards
The wildcard ? represents an unknown type and is commonly used in generic method parameters to increase flexibility.
Upper bounded wildcard (
? extends Type): Represents the parameter type isTypeor any of its subtypes. Such collections are read-only (cannot add elements exceptnull) because we cannot determine the exact type of the collection.java// Can accept List<Integer>, List<Double>, etc. public static double sumOfList(List<? extends Number> list) { double sum = 0.0; for (Number n : list) { sum += n.doubleValue(); } return sum; }Lower bounded wildcard (
? super Type): Represents the parameter type isTypeor any of its supertypes. Such collections are write-only (can add instances ofTypeand its subtypes), but reading only returnsObject.java// Can accept List<Integer>, List<Number>, List<Object> public static void addIntegers(List<? super Integer> list) { list.add(1); list.add(2); }Unbounded wildcard (
?): Represents any type. Used when the type doesn't matter, such asList<?>.
Type Erasure
Java generics are implemented through type erasure. This means that after compilation, all generic type information is removed. Box<String> and Box<Integer> at runtime both become the raw Box class, with the type parameter T replaced by its bound (default is Object). The compiler automatically inserts type casting code where necessary to ensure type safety.