Java Generics
You have been using generics since ArrayList: the <String> in ArrayList<String> tells Java which type the list holds. The compiler then checks every add, and get gives back a String without any cast. Generics let one class or method work with many types while keeping that safety. In this topic you write your own.
Why generics
Without generics, a container would have to store Object, and every read would need a cast that may fail while the program runs. With generics, the mistake is caught by the compiler:
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<>();
names.add("Asha");
names.add(42);
}
}Main.java:7: error: incompatible types: int cannot be converted to String
A generic class
Put a type parameter in angle brackets after the class name. By convention it is a single capital letter: T for type, E for element, K and V for key and value. Inside the class, T is used like any other type:
class Box<T> {
private T value;
Box(T value) {
this.value = value;
}
T get() {
return value;
}
boolean isEmpty() {
return value == null;
}
}
public class Main {
public static void main(String[] args) {
Box<String> word = new Box<>("Java");
Box<Integer> number = new Box<>(23);
System.out.println(word.get().length());
System.out.println(number.get() + 1);
System.out.println(new Box<String>(null).isEmpty());
}
}4 24 true
word.get() is known to be a String and number.get() an Integer, so no casts are needed. Generic types work only with classes, which is why numbers use the wrapper classes.
A class can have several type parameters, like a pair of two values:
class Pair<A, B> {
final A first;
final B second;
Pair(A first, B second) {
this.first = first;
this.second = second;
}
@Override
public String toString() {
return "(" + first + ", " + second + ")";
}
}
public class Main {
public static void main(String[] args) {
Pair<String, Integer> p = new Pair<>("age", 31);
System.out.println(p);
System.out.println(p.first.toUpperCase() + " " + (p.second + 1));
}
}(age, 31) AGE 32
Generic methods
A single method can be generic too. The type parameter is written before the return type:
import java.util.List;
public class Main {
static <T> T last(List<T> items) {
return items.get(items.size() - 1);
}
static <T extends Comparable<T>> T largest(List<T> items) {
T best = items.get(0);
for (T item : items) {
if (item.compareTo(best) > 0) {
best = item;
}
}
return best;
}
public static void main(String[] args) {
System.out.println(last(List.of("a", "b", "c")));
System.out.println(largest(List.of(4, 19, 7)));
System.out.println(largest(List.of("pear", "apple", "plum")));
}
}c 19 plum
<T extends Comparable<T>> is a bounded type: largest works only for types that can be compared, so compareTo from the Interfaces topic is allowed inside.
Wildcards
A List<Integer> is not a List<Number>, even though an Integer is a Number. To accept a list of any kind of number, use a wildcard: List<? extends Number> means "a list of some type that is a Number":
import java.util.List;
public class Main {
static double sum(List<? extends Number> numbers) {
double total = 0;
for (Number n : numbers) {
total += n.doubleValue();
}
return total;
}
public static void main(String[] args) {
System.out.println(sum(List.of(1, 2, 3)));
System.out.println(sum(List.of(1.5, 2.5)));
}
}6.0 4.0
You will read wildcards much more often than you write them; Java's own library uses them a lot.
Practice exercises
- Write a generic class
Stack<T>withpush,popandisEmpty, using anArrayList<T>inside. - Write a generic method
swap(T[] array, int i, int j)and test it with strings and numbers. - Write
<T extends Comparable<T>> boolean isSorted(List<T> list).