Loslegen
Javaneer
Zurück zur Stufe
Stufe 2·Objektorientierte Programmierung

Records, versiegelte Klassen & Enums

Das moderne Java-Typsystem: Records, versiegelte Hierarchien, Pattern Matching und Enums.

18 Min. LesezeitFortgeschritten

Deutsche Übersetzung in Arbeit

Diese Lektion ist noch nicht ins Deutsche übersetzt und wird daher auf Englisch angezeigt. Der Rest der Seite ist vollständig lokalisiert.

Auf dieser Seite

Modern Java (16+) added concise tools that remove boilerplate and make your types safer and clearer: records, sealed classes, and enums. They help you say more with less code.

Enums: a fixed set of named values

An enum defines a type with a fixed set of possible values. Instead of using loose strings or magic numbers, you get a small, safe, self-documenting set.

An enum is like the gears in a car

A car's gearstick has a fixed set of positions: Park, Reverse, Neutral, Drive. You can't shift into "Banana". An enum is that gearstick - it constrains a value to a known, valid set, so invalid states are impossible.

enum Day {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
Day today = Day.WEDNESDAY;

String plan = switch (today) {
    case SATURDAY, SUNDAY -> "Relax";
    default -> "Work";
};
System.out.println(plan);   // Work

Enums can also carry data and methods:

enum Planet {
    EARTH(9.81), MARS(3.71);

    private final double gravity;
    Planet(double gravity) { this.gravity = gravity; }
    double weight(double mass) { return mass * gravity; }
}

Prefer enums over string constants

Using "WEDNESDAY" as a raw string invites typos ("WENSDAY") that compile fine but fail at runtime. An enum catches such mistakes at compile time and enables exhaustive switch handling. Reach for enums whenever a value has a fixed set of options.

Records: data classes without the boilerplate

Before records, a simple data holder required a constructor, getters, equals(), hashCode(), and toString() - dozens of lines for something trivial. A record generates all of that automatically.

record Point(int x, int y) { }

That single line gives you:

  • a constructor Point(int x, int y)
  • accessor methods x() and y()
  • sensible equals(), hashCode(), and toString()
  • immutability - a record's fields can't change after creation
Point p = new Point(3, 4);
System.out.println(p.x());     // 3
System.out.println(p);         // Point[x=3, y=4]

Point q = new Point(3, 4);
System.out.println(p.equals(q));  // true - value equality for free!
A record is like a printed form

A record is like a filled-out form: name here, address there - a fixed set of fields, filled once, then read many times. It's about carrying data, not behaving. When your class is really just data, make it a record.

Sealed classes: controlling who can extend

A sealed class or interface restricts which classes may extend or implement it. You explicitly list the permitted subtypes - no surprises.

sealed interface Shape permits Circle, Rectangle { }

record Circle(double radius) implements Shape { }
record Rectangle(double width, double height) implements Shape { }

Now Shape has exactly two implementations, and Java knows it. This pairs beautifully with switch pattern matching, which can be exhaustive - the compiler guarantees you've handled every case:

double area = switch (shape) {
    case Circle c    -> Math.PI * c.radius() * c.radius();
    case Rectangle r -> r.width() * r.height();
    // no 'default' needed - the compiler knows these are the only options
};

Why 'sealing' is powerful

Sealed types let you say "this is a closed set of possibilities." Combined with records and pattern matching, they make modeling data - like the results of an operation, or the shapes in a drawing app - remarkably safe and expressive. Add a new subtype, and the compiler flags every switch you forgot to update.

Putting it together

sealed interface Payment permits Cash, Card { }
record Cash(double amount) implements Payment { }
record Card(double amount, String number) implements Payment { }

String describe(Payment p) {
    return switch (p) {
        case Cash c -> "Cash: $" + c.amount();
        case Card c -> "Card ending " + c.number();
    };
}

Concise, immutable, type-safe, and exhaustive - modern Java at its best.

Quick check

What does declaring 'record Point(int x, int y) {}' give you automatically?

Key takeaways

  • Enums define a fixed, type-safe set of named values and can carry data and methods.
  • Prefer enums over raw string or int constants - mistakes are caught at compile time.
  • Records are concise, immutable data classes that auto-generate constructor, accessors, equals, hashCode, and toString.
  • Sealed classes/interfaces restrict which types may extend them via a 'permits' list.
  • Sealed types plus record patterns enable exhaustive switch - the compiler ensures every case is handled.

One more essential OOP topic remains: how objects are compared for equality, and why immutability makes everything simpler. That's next.