Declaring Generic Classes and Methods
Generic Classes
A generic class declares one or more type parameters (conventionally single uppercase letters) in angle brackets after the class name:
class Box<T> {
private T value;
void set(T value) { this.value = value; }
T get() { return value; }
}
T is a type parameter — a placeholder for a concrete type supplied at the point of use. Inside the class body, T can be used as a type wherever a concrete type would appear: field types, method parameter types, return types, local variable types.
Instantiation
Box<String> stringBox = new Box<>();
stringBox.set("hello");
String s = stringBox.get(); // no cast needed
The diamond operator <> was introduced in Java 7. It infers the type argument from the left-hand side, avoiding the verbose new Box<String>().
Multiple Type Parameters
class Pair<K, V> {
private K key;
private V value;
Pair(K key, V value) {
this.key = key;
this.value = value;
}
K getKey() { return key; }
V getValue() { return value; }
}
Pair<String, Integer> entry = new Pair<>("Alice", 25);
Convention for type parameter names:
E— Element (used by collections)K— KeyV— ValueN— NumberT,S,U,V— arbitrary types (in order)
Generic Methods
A method can declare its own type parameters, independent of any class-level parameters:
public class Utils {
public static <T> void printArray(T[] arr) {
for (T item : arr) {
System.out.print(item + " ");
}
System.out.println();
}
}
The <T> before the return type declares the type parameter for this method. The method can now be called with any array type:
String[] names = {"Alice", "Bob"};
Integer[] numbers = {1, 2, 3};
Utils.printArray(names); // T inferred as String
Utils.printArray(numbers); // T inferred as Integer
The compiler infers T from the argument types. You can also explicitly specify it: Utils.<String>printArray(names), though this is rarely necessary.
Bounded Type Parameters
Sometimes you want to restrict which types can be supplied as arguments. Use the extends keyword:
class NumericBox<T extends Number> {
private T value;
NumericBox(T value) { this.value = value; }
double getDouble() {
return value.doubleValue(); // valid because T extends Number
}
}
T extends Number means T must be Number or any subclass of Number (e.g., Integer, Double, BigDecimal). The bounding gives the generic code access to Number’s methods — you can call .doubleValue(), .intValue(), etc. on a T variable.
Bounding by an Interface
Despite the extends keyword, the bound can be an interface:
class SortedBox<T extends Comparable<T>> {
private T value;
int compare(T other) {
return value.compareTo(other); // valid — T implements Comparable
}
}
Multiple Bounds
A type parameter can have multiple bounds separated by &:
class DataProcessor<T extends Number & Comparable<T> & Serializable> {
// T must be a Number, Comparable, and Serializable
}
If a class bound appears, it must come first (before any interface bounds). At most one class bound is allowed (Java has single inheritance).
Generic Constructors
Constructors can also be generic, even if the class is not:
class Pair {
<T, U> Pair(T first, U second) {
System.out.println(first + " / " + second);
}
}
new Pair("hello", 42); // T=String, U=Integer
new Pair(3.14, new Date()); // T=Double, U=Date
Inheritance and Generics
A class can extend a generic class, providing concrete type arguments:
class StringBox extends Box<String> {
// StringBox inherits set(String) and String get()
}
Or it can remain generic and pass the parameter through:
class EnhancedBox<T> extends Box<T> {
boolean isEmpty() { return get() == null; }
}
A generic class can also introduce additional type parameters:
class LabelledBox<T, L> extends Box<T> {
private L label;
L getLabel() { return label; }
void setLabel(L label) { this.label = label; }
}
Static Context
Static fields and methods in a generic class cannot use the class’s type parameter — because they are shared across all instantiations of the class:
class Container<T> {
static T defaultElement; // COMPILE ERROR — which T would this be?
static <U> U identity(U value) {
return value; // fine — U is declared on this method, not the class
}
}
The class type parameter T is associated with an instance of Container. A static method belongs to the class itself, not any particular instance, so T has no meaning in that context.