Generic classes & interfaces in Java
class Box<T>, type parameter naming (T, E, K, V).
A box with a blank label
A generic class declares type parameters in angle brackets: class Box<T>. Inside the class, **T is a placeholder** meaning "whatever type the user picks".
class Box<T> {
private T item;
void put(T item) { this.item = item; }
T get() { return item; }
}Filling in the blank
When someone writes **Box<String>, String is the type argument**, and every T effectively becomes String: put accepts Strings and get returns a String, so you can call String methods with no cast.
Box<String> b = new Box<>();
b.put("gift");
b.get().toUpperCase(); // → GIFTYour turn
What does this print?
class Box<T> {
private T item;
void put(T item) { this.item = item; }
T get() { return item; }
}
void main() {
Box<Integer> b = new Box<>();
b.put(20);
System.out.println(b.get() + 1);
}20121Compile error
Show the answer
For Box<Integer>, get() returns an Integer, so b.get() + 1 is arithmetic: 21.
Wrong thing in the box
What happens?
class Box<T> {
private T item;
void put(T item) { this.item = item; }
}
void main() {
Box<String> b = new Box<>();
b.put(5);
}Runs fineCompile errorThrows ClassCastException
Show the answer
In a Box<String>, put expects a String. Passing an int is rejected by the compiler.
Making a reusable box
class Box {
Object item;
}
String s = (String) box.item; // castAccepts anything, so mistakes only show up as ClassCastException.
class Box<T> {
T item;
}
String s = box.item; // checked, no castOne class, any type, checked by the compiler.
Names and multiple parameters
Convention: one capital letter. T = any type, E = element of a collection, K = key, V = value, R = result. A class can take several: class Pair<K, V>. Interfaces can be generic too: Comparable<T>, Map<K, V>.
interface Pair<K, V> {
K key();
V value();
}A generic interface
What does this print?
interface Pair<K, V> { K key(); V value(); }
record Score(String key, Integer value)
implements Pair<String, Integer> {}
void main() {
Pair<String, Integer> p =
new Score("pts", 10);
IO.println(p.key() + "=" + (p.value() + 1));
}pts=101pts=11Compile error
Show the answer
The record implements Pair<String, Integer>, so through p, **V is Integer**: value() returns a number, and (p.value() + 1) is arithmetic: 11.
You use these daily
List<E>, Map<K, V>, Optional<T>, CompletableFuture<T>, ResponseEntity<T> in Spring: real-world APIs are full of generic classes. Once you can read class Box<T>, you can read the signatures of almost every library you'll ever use.
Key takeaways
- Declare: class Box<T> { T item; }
- Use: Box<String> b = new Box<>();
- Naming convention: T type, E element, K key, V value, R result
- A class can have several parameters: class Pair<K, V>
💡 A generic class is a cookie cutter with an empty slot: Box<String> and Box<Integer> are cookies made from the same cutter.
Type parameter names don't have to be single letters: class Box<Item> compiles fine. The one-capital-letter convention exists so they can't be mistaken for real class names.
Practice questions
What does this print?
class Box<T> {
private T item;
void put(T item) { this.item = item; }
T get() { return item; }
}
void main() {
Box<String> b = new Box<>();
b.put("gift");
System.out.println(b.get().toUpperCase());
}- gift
- GIFT
- Compile error
- Throws ClassCastException
Check your answer
GIFT. For Box<String>, get() returns a String, so you can call toUpperCase() directly with no cast.
What happens with this code?
class Box<T> {
private T item;
void put(T item) { this.item = item; }
}
void main() {
Box<String> b = new Box<>();
b.put(5);
}- Runs fine
- Compile error
- Throws ClassCastException
- Throws IllegalArgumentException
Check your answer
Compile error. In Box<String>, put expects a String. Passing an int is rejected by the compiler.