V
Vel·ToolKit
Simple · Fast · Ready to use
EN
Chapter 10 of 20

Enums & Records

Advanced enums, record auto-boilerplate, sealed classes

Basic enum

enum defines a fixed set of constant values. Each enum constant is essentially a static instance of that enum type, fixed at compile time, type-safe (the compiler blocks invalid values), and usable in switch.

// EnumBasic.java
public class EnumBasic {
    enum Status { PENDING, ACTIVE, DONE, CANCELLED }

    public static void main(String[] args) {
        Status s = Status.ACTIVE;
        System.out.println(s);              // ACTIVE
        System.out.println(s.name());       // ACTIVE
        System.out.println(s.ordinal());    // 1 (declaration order)

        // iterate all values
        for (Status v : Status.values()) {
            System.out.println(v);
        }

        // restore from a string (throws IllegalArgumentException if not found)
        Status parsed = Status.valueOf("DONE");
        System.out.println(parsed);
    }
}

enum + switch Expression

The switch expression (Java 14+) pairs especially well with enum: the compiler checks exhaustiveness — if all cases are listed, default can be omitted; a missing case is a compile error.

// EnumSwitch.java
public class EnumSwitch {
    enum Color { RED, GREEN, BLUE }

    static String hex(Color c) {
        return switch (c) {
            case RED   -> "#ff0000";
            case GREEN -> "#00ff00";
            case BLUE  -> "#0000ff";
        };
    }

    public static void main(String[] args) {
        for (Color c : Color.values()) {
            System.out.println(c + " " + hex(c));
        }
    }
}

enum with Fields and Methods

An enum is also a class — it can have fields, constructors, and methods. Each constant calls the constructor via NAME(args) at declaration. Common for "enums with metadata".

// HttpStatus.java
public class HttpStatus {
    enum Code {
        OK(200, "OK"),
        NOT_FOUND(404, "Not Found"),
        SERVER_ERROR(500, "Internal Server Error");

        final int value;
        final String reason;

        Code(int value, String reason) {
            this.value = value;
            this.reason = reason;
        }

        boolean isError() {
            return value >= 400;
        }
    }

    public static void main(String[] args) {
        for (Code c : Code.values()) {
            System.out.printf("%d %s (error=%b)%n", c.value, c.reason, c.isError());
        }
    }
}

enum Implementing an Interface

An enum can implements an interface so each constant has specific behavior — an elegant "strategy pattern" implementation.

// EnumStrategy.java
public class EnumStrategy {
    interface BinaryOp {
        int apply(int a, int b);
    }

    enum Op implements BinaryOp {
        ADD { @Override public int apply(int a, int b) { return a + b; } },
        SUB { @Override public int apply(int a, int b) { return a - b; } },
        MUL { @Override public int apply(int a, int b) { return a * b; } };
    }

    public static void main(String[] args) {
        for (Op op : Op.values()) {
            System.out.println(op + "(2,3) = " + op.apply(2, 3));
        }
    }
}

record (Java 14+, final in Java 16)

record is syntactic sugar for an "immutable data carrier". One line of declaration gives you for free: private final fields, the canonical constructor, accessors (not getX but x()), equals, hashCode, toString. It massively reduces DTO boilerplate.

// RecordBasic.java
public class RecordBasic {
    record Point(int x, int y) {}

    public static void main(String[] args) {
        Point p1 = new Point(1, 2);
        Point p2 = new Point(1, 2);

        System.out.println(p1.x());            // the accessor is x(), not getX()
        System.out.println(p1);                // Point[x=1, y=2]  (auto toString)
        System.out.println(p1.equals(p2));     // true             (auto equals)
        System.out.println(p1.hashCode() == p2.hashCode()); // true (auto hashCode)
    }
}

record Compact Constructor (Validation)

A record's default constructor assigns parameters to same-named fields. To add validation, write a "compact constructor" — no parameter list, just validation code inside { }. Fields are assigned automatically at the end.

// RecordValidate.java
public class RecordValidate {
    record Range(int low, int high) {
        public Range {                              // compact constructor
            if (low > high) {
                throw new IllegalArgumentException("low > high");
            }
        }

        // you can add extra methods
        public boolean contains(int x) {
            return x >= low && x <= high;
        }
    }

    public static void main(String[] args) {
        Range r = new Range(1, 10);
        System.out.println(r.contains(5));     // true
        try {
            new Range(10, 1);
        } catch (IllegalArgumentException e) {
            System.out.println("bad: " + e.getMessage());
        }
    }
}

sealed Classes and Interfaces (Java 17+)

sealed restricts "which classes can extend me". Combined with record and switch pattern matching, it can express "algebraic data types" — a feature common in Kotlin / Rust that Java finally has.

// SealedDemo.java
public class SealedDemo {
    sealed interface Shape permits Circle, Square, Triangle {}
    record Circle(double r) implements Shape {}
    record Square(double side) implements Shape {}
    record Triangle(double base, double height) implements Shape {}

    static double area(Shape s) {
        // Java 17 uses the instanceof pattern; since Java 21 you can use case Circle c -> in a switch expression
        if (s instanceof Circle c)        return Math.PI * c.r() * c.r();
        else if (s instanceof Square sq)  return sq.side() * sq.side();
        else if (s instanceof Triangle t) return 0.5 * t.base() * t.height();
        throw new IllegalStateException("unreachable");
    }

    public static void main(String[] args) {
        Shape[] shapes = { new Circle(2), new Square(3), new Triangle(4, 5) };
        for (Shape s : shapes) {
            System.out.printf("%s -> %.2f%n", s, area(s));
        }
    }
}