Java Generic Classes
Define classes that take type parameters in Java using the <T> generic syntax.
Java Generic Classes
A generic class is a class whose declaration carries one or more type parameters — placeholders that the caller fills in when they create an instance. The same class body then describes a whole family of types: Box<String>, Box<Integer>, Box<User> are all distinct compile-time types that share one source. This is the most common shape generics take, and it's how every collection in java.util, every Optional, every Future, and every CompletableFuture is written.
The syntax
The type parameter list goes between the class name and the body, in angle brackets:
public class Box<T> {
private T value;
public Box(T value) { this.value = value; }
public T get() { return value; }
public void set(T value) { this.value = value; }
}Read the declaration as "a Box parameterised over some type T." Inside the class, T behaves like any other type — you can declare fields of type T, methods returning T, parameters of type T. The compiler treats it as a real, unknown type until the caller picks one.
At the call site, you supply the actual type:
Box<String> greeting = new Box<>("hello");
Box<Integer> answer = new Box<>(42);
String s = greeting.get(); // already a String — no cast
int i = answer.get(); // auto-unboxed from IntegerThe <> on the right is the diamond operator — the compiler infers the type argument from the left-hand declaration. You can write new Box<String>("hello") explicitly, but you almost never need to.
Multiple type parameters
A class can declare more than one type parameter. The classic example is a key/value pair:
public class Entry<K, V> {
private final K key;
private final V value;
public Entry(K key, V value) {
this.key = key;
this.value = value;
}
public K key() { return key; }
public V value() { return value; }
}
Entry<String, Integer> score = new Entry<>("Ada", 100);
String name = score.key();
int n = score.value();The convention is single-letter names — K for key, V for value, E for element, R for return, T for "generic type." When you need more clarity (rare), longer names are allowed: Map<KeyType, ValueType> is legal, just unfashionable.
Generic constructors
The type parameter is fixed by the instance, so every constructor of a generic class already has access to T:
public class Pair<T> {
private final T first;
private final T second;
public Pair(T first, T second) { this.first = first; this.second = second; }
public Pair(T both) { this(both, both); }
}Constructors themselves can also be generic over additional type parameters that aren't on the class — but that's uncommon enough to belong in the next chapter on generic methods.
Generic classes can extend each other
A subclass can inherit from a generic class in three ways. Each one means something different:
// 1. Lock the parent's type parameter — concrete subclass for one element type.
public class StringList extends ArrayList<String> { ... }
// 2. Pass the type parameter through — the subclass is still generic.
public class MyList<E> extends ArrayList<E> { ... }
// 3. Add new type parameters of your own.
public class TaggedList<E, Tag> extends ArrayList<E> { ... }The middle form is the most common — you propagate the parent's parameter to your own callers. The first form is what you do when the subclass is specialised: a tree of node-of-strings only.
Fields and the type parameter
Each Box<...> instance carries its own T. The bytecode doesn't — at runtime the JVM sees just Box (this is type erasure, covered later in this part). The consequence is that the type parameter belongs to the instance, not to the class object:
Box<String> a = new Box<>("hi");
Box<Integer> b = new Box<>(5);
a.getClass() == b.getClass(); // true — both are class BoxThat's a useful fact to keep in mind: Box<String> and Box<Integer> are different types to the compiler but the same class at runtime. We'll come back to this in Java Type Erasure.
Static members can't see the type parameter
Static fields and static methods belong to the class, not to any one instance — so they can't see the instance's T. This is illegal:
public class Box<T> {
private static T defaultValue; // ❌ won't compile — no T at the static level
public static T empty() { ... } // ❌ same problem
}A static method that needs a type parameter has to declare its own, independent of the class's. That's the subject of the next chapter.
Designing your own: a small typed stack
A worked, working class to bring it all together — a generic Stack with push, pop, peek, and size. It's parameterised over E (element), backed by an Object[] internally (because of generic array restrictions), and the unchecked cast on pop is the kind of well-contained workaround you'll see in real code.
The @SuppressWarnings("unchecked") annotations sit on the two reads that have to cast from Object back to E. Those casts are safe — push only ever stores values of type E — but the compiler can't see that, because erasure has stripped E from the bytecode. Suppressing the warning locally, on the smallest possible scope, is the right move.
What's next
You've seen the class-level parameter. Sometimes you need a single method to be generic, with its own type parameter that's independent of the class — useful for utility methods, static helpers, and any operation whose type relationship lives only inside that one method. Continue to Java Generic Methods.
Practice
You write `public class Box<T> { private static T value; }`. The compiler rejects it. Why?