Java Generics
Java generics let you write classes, interfaces, and methods that work with a type chosen later. They make code reusable while still letting the compiler check that the right types are used.
You have already seen generics in collections such as ArrayList<String> and HashMap<String, Integer>. The type inside angle brackets tells Java what kind of values the object should store or process.
Why Use Generics?
Without generics, code often has to use Object, which can hold any reference type. That sounds flexible, but it also means you may need casts and may not find type mistakes until the program runs.
Generics move those checks to compile time. For example, an ArrayList<String> accepts strings, and the compiler rejects code that tries to add an integer to that list.
Generic Class Example
A generic class declares a type parameter in angle brackets after the class name. A common name is T, which stands for type, but the name can be any valid identifier.
public class Main {
static class Box<T> {
private T value;
Box(T value) {
this.value = value;
}
T getValue() {
return value;
}
}
public static void main(String[] args) {
Box<String> messageBox = new Box<>("Hello");
Box<Integer> numberBox = new Box<>(42);
System.out.println(messageBox.getValue());
System.out.println(numberBox.getValue());
}
}
Output:
Hello
42
The same Box class is used for both String and Integer. When you write Box<String>, Java treats T as String for that object. When you write Box<Integer>, Java treats T as Integer.
The Diamond Operator
The empty angle brackets in new Box<>("Hello") are called the diamond operator. Java can infer the type from the variable declaration, so you do not need to repeat String on the right side.
Generic Methods
A method can also declare its own type parameter. Put the type parameter before the return type.
public class Main {
public static <T> void printPair(T first, T second) {
System.out.println(first + " and " + second);
}
public static void main(String[] args) {
printPair("red", "blue");
printPair(10, 20);
}
}
Output:
red and blue
10 and 20
Here, T belongs to the method, not to the whole class. The first call uses String, and the second call uses Integer.
Bounded Type Parameters
Sometimes a generic type should not be completely open. A bounded type parameter limits what types are allowed. Use extends to say that the type must be a certain class or implement a certain interface.
public class Main {
public static <T extends Number> double add(T first, T second) {
return first.doubleValue() + second.doubleValue();
}
public static void main(String[] args) {
System.out.println(add(4, 6));
System.out.println(add(2.5, 3.5));
}
}
Output:
10.0
6.0
The bound T extends Number allows numeric wrapper classes such as Integer and Double. It also lets the method call doubleValue(), because every Number has that method.
Wildcards
A wildcard uses ? when you want to accept a generic type without naming an exact type parameter. For example, List<?> means a list of some unknown type, and List<? extends Number> means a list whose elements are Number objects or a subclass of Number.
Wildcards are useful in method parameters when the method only needs to read values from a generic object. If the method needs to add values, a named type parameter or a more specific type is often clearer.
Common Generic Type Names
| Name | Common meaning |
|---|---|
T |
Type |
E |
Element, often used by collections |
K |
Key, often used by maps |
V |
Value, often used by maps |
Common Mistakes
- Forgetting that generics work with reference types, so use
Integerinstead ofint. - Using a raw type such as
ArrayListinstead ofArrayList<String>. - Assuming
List<Integer>can be assigned toList<Number>. Generic types are stricter than normal inheritance. - Adding casts that generics are meant to avoid.
Takeaway: generics let you write reusable code while keeping strong type checks, which is why Java collections and many library APIs use them heavily.
