Generics and enums
Exceptions, collections and modern Java12 min readLesson 5 of 6
5.1 Type parameters
Generics let a class or method work with a type chosen at use time. Box<T> declares a type parameter T; Box<String> uses it with String. The compiler checks every value that goes in and comes out.
Without generics, a collection would store Object and every read would need a cast. Generics move that check to compile time and remove most casts from the source.
A type parameter is a placeholder for a type. You cannot create an array of T or call new T(). At runtime, most generic type information is erased, which is why these limits exist.
public class Main {
public static void main(String[] args) {
Box<String> names = new Box<>();
names.set("Asha");
System.out.println(names.get());
Box<Integer> counts = new Box<>();
counts.set(12);
System.out.println(counts.get() + 1);
}
}
class Box<T> {
private T value;
void set(T value) { this.value = value; }
T get() { return value; }
}Output
Asha 13
5.1.1 Generic classes and methods
A generic class puts the parameter after the class name: class Box<T>. Fields and methods in the class can use T. Create an instance with new Box<String>() or new Box<>() when the compiler can infer the type.
A generic method declares its own parameter before the return type: static <T> T first(List<T> values). The parameter is local to the method and can differ from any class parameter.
A bounded type narrows what T can be: <T extends Comparable<T>> allows compareTo. Bounds give the method the operations it needs while keeping the caller's type.
import java.util.List;
public class Main {
public static void main(String[] args) {
System.out.println(larger(4, 9));
System.out.println(larger("apple", "pear"));
System.out.println(total(List.of(1, 2, 3)));
}
static <T extends Comparable<T>> T larger(T a, T b) {
return a.compareTo(b) >= 0 ? a : b;
}
static double total(List<? extends Number> values) {
double sum = 0;
for (Number value : values) {
sum += value.doubleValue();
}
return sum;
}
}Output
9 pear 6.0
5.1.2 Wildcards
List<? extends Number> accepts a list of Number or any subclass, such as Integer. It is read-only for the element type: reading gives a Number, but adding is rejected because the exact type is unknown.
List<? super Integer> accepts a list of Integer or a supertype, such as Number or Object. It can accept an Integer, and reading gives an Object.
Use an upper bound when a method only reads values. Use a lower bound when it writes values. A plain List<T> in a generic method is often simpler than a wildcard.
5.1.3 Enums
An enum declares a fixed set of constants: enum Priority { LOW, MEDIUM, HIGH }. Each constant is a single object of the enum type. Enums are type-safe, so a method that takes Priority cannot receive an unrelated string.
values() returns all constants in declaration order. valueOf("HIGH") returns the constant with that name and throws IllegalArgumentException for an unknown name. ordinal() returns the zero-based position, which is useful for sorting but should not be stored as a stable identifier.
An enum can have fields, a constructor, and methods. The constructor is implicitly private, and constants pass arguments to it.
public class Main {
public static void main(String[] args) {
Priority priority = Priority.HIGH;
System.out.println(priority);
System.out.println(priority.ordinal());
System.out.println(Priority.valueOf("LOW"));
for (Priority p : Priority.values()) {
System.out.println(p);
}
}
}
enum Priority {
LOW, MEDIUM, HIGH
}Output
HIGH 2 LOW LOW MEDIUM HIGH
5.1.4 Enums in switches and maps
A switch over an enum can list the constants directly. The arrow form with case PRO -> does not fall through. A switch expression that covers every constant needs no default, and the compiler checks that coverage.
Enums work well as map keys and set elements because each constant has one identity. EnumMap and EnumSet are specialized implementations with compact storage.
Use an enum for a small, closed set of values: status, priority, day of week, plan level. Use a class or a string when the set can grow from configuration or user input.
public class Main {
public static void main(String[] args) {
for (Plan plan : Plan.values()) {
System.out.println(plan + ": " + plan.monthly());
}
Plan plan = Plan.PRO;
switch (plan) {
case PRO -> System.out.println("Team plan");
default -> System.out.println("Personal plan");
}
}
}
enum Plan {
FREE(0), PRO(499), TEAM(1499);
private final int price;
Plan(int price) { this.price = price; }
int monthly() { return price; }
}Output
FREE: 0 PRO: 499 TEAM: 1499 Team plan
5.1.5 Several type parameters
A class can declare more than one type parameter, as in Pair<A, B>. Each parameter is independent, so a pair can hold a string and an integer without losing either type.
Order matters at the declaration: Pair<String, Integer> is not the same type as Pair<Integer, String>. The compiler checks the order at every use.
Generic types compose with the collections framework. A Map<String, List<Integer>> is a map from a name to a list of numbers, and the type prevents putting a single integer where a list belongs.
public class Main {
public static void main(String[] args) {
Pair<String, Integer> score = new Pair<>("Asha", 92);
System.out.println(score.first + " " + score.second);
}
}
class Pair<A, B> {
final A first;
final B second;
Pair(A first, B second) {
this.first = first;
this.second = second;
}
}Output
Asha 92
5.1.6 Practice
- Create a generic
Box<T>and store a string and an integer in separate boxes. - Write
static <T extends Comparable<T>> T larger(T a, T b)and test it with numbers and strings. - Write a method that sums a
List<? extends Number>. - Create an enum
Priorityand printvalues(),ordinal(), andvalueOf. - Give the enum a price field and a
monthly()method, then print each constant with its price.
For step 2, call larger with two strings and check that the result is the one that sorts later.
