Generic classes and methods
You have used generics since Collections: List<String> is a list that the compiler knows holds only strings. Writing your own generic type works the same way. A type parameter in angle brackets — by convention a single capital letter such as T (type), E (element), K/V (key/value) — stands for a type the user chooses later.
public class Main {
public static void main(String[] args) {
Box<String> name = new Box<>("Ada");
Box<Integer> count = new Box<>(3);
String s = name.get(); // no cast needed
int n = count.get() + 1; // unboxed automatically
System.out.println(s + " " + n);
Pair<String, Double> price = new Pair<>("coffee", 3.5);
System.out.println(price.first() + " costs " + price.second());
}
}
class Box<T> {
private T value;
Box(T value) { this.value = value; }
T get() { return value; }
void set(T value) { this.value = value; }
}
record Pair<A, B>(A first, B second) { }Ada 4
coffee costs 3.5Try name.set(42); — the compiler stops you, because name is a Box<String>.
Before generics (Java 5) collections held plain Objects, and every read needed a cast that could fail at runtime. Generics move that check to compile time: the mistake below is caught before the program ever runs.
no suitable method found for add(String)
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Integer> ids = new ArrayList<>();
ids.add(101);
ids.add("102");
System.out.println(ids);
}
}Main.java:7: error: no suitable method found for add(String)
ids.add("102");
^
method List.add(Integer) is not applicable
(argument mismatch; String cannot be converted to Integer)
method List.add(int,Integer) is not applicable
(actual and formal argument lists differ in length)
1 errorThe list was declared to hold Integers, and "102" is a String. Without generics this would be accepted and blow up later with a ClassCastException wherever someone read the element as a number.
Convert the data at the boundary: ids.add(Integer.parseInt("102"));.
Generic methods
A single method can be generic even inside a non-generic class. Put the type parameter before the return type: static <T> T firstOr(List<T> list, T fallback). The compiler infers T from the arguments at each call.
import java.util.*;
public class Main {
static <T> T firstOr(List<T> list, T fallback) {
return list.isEmpty() ? fallback : list.get(0);
}
static <T> List<T> repeat(T item, int times) {
List<T> out = new ArrayList<>();
for (int i = 0; i < times; i++) out.add(item);
return out;
}
public static void main(String[] args) {
System.out.println(firstOr(List.of("x", "y"), "none"));
System.out.println(firstOr(List.<String>of(), "none"));
System.out.println(repeat('*', 5));
System.out.println(repeat(List.of(1, 2), 2));
}
}x
none
[*, *, *, *, *]
[[1, 2], [1, 2]]