xSKILLby Infirals
Register

Java for Beginners

0 of 24 notes read 0%

Reading progress is saved to your INFIRALS account. Sign in to keep this note marked as read.

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.

java
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.

java
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.

java
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.

java
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.

java
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

  1. Create a generic Box<T> and store a string and an integer in separate boxes.
  2. Write static <T extends Comparable<T>> T larger(T a, T b) and test it with numbers and strings.
  3. Write a method that sums a List<? extends Number>.
  4. Create an enum Priority and print values(), ordinal(), and valueOf.
  5. 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.

Interview questions

3 questions from this note. Open one to see the answer; no account needed.

  • 01What does a generic type parameter give you?

    It lets a class or method work with a type chosen by the caller while the compiler checks every use. It removes the need for casts at read sites.

  • 02What does `? extends Number` allow?

    It accepts a list of `Number` or any subclass. Values can be read as `Number`, but adding to the list is not allowed.

  • 03How do `values()` and `valueOf()` differ?

    `values()` returns all constants in declaration order. `valueOf(name)` returns the constant with that exact name or throws `IllegalArgumentException`.