A record is a compact, immutable data class: record Result(String student, int score) {} gives you a constructor, accessors (score()), equals, hashCode and toString. Add validation in a compact constructor.
An enum is a fixed set of named constants, which can have fields and methods — perfect for grades, payment methods or statuses.
A sealed interface lists exactly which types may implement it. Combined with pattern matching in switch, the compiler checks that every case is handled: add a new payment type and every switch that forgot it stops compiling.
import java.util.List;
public class Payments {
public static void main(String[] args) {
List<Payment> payments = List.of(
new MobileMoney("0712345678", 150_000),
new BankTransfer("CRDB", "TX-88231", 300_000),
new Cash(50_000)
);
long total = 0;
for (Payment p : payments) {
System.out.println(describe(p));
total += p.amount();
}
System.out.println("Total: " + total);
for (Grade g : Grade.values()) {
System.out.println(g + " from " + g.minScore + ": " + g.remark);
}
System.out.println(Grade.forScore(68) + " " + Grade.valueOf("A").remark);
Result r = new Result("Amina", 88);
System.out.println(r + " " + r.score() + " " + r.equals(new Result("Amina", 88)));
try {
new Result("Juma", 120);
} catch (IllegalArgumentException e) {
System.out.println("Rejected: " + e.getMessage());
}
}
static String describe(Payment p) {
return switch (p) {
case MobileMoney m -> "Mobile money from " + m.phone() + ": " + m.amount();
case BankTransfer b when b.amount() >= 250_000 -> "LARGE bank transfer " + b.reference();
case BankTransfer b -> "Bank transfer via " + b.bank() + ": " + b.amount();
case Cash c -> "Cash: " + c.amount();
}; // no default needed: the sealed interface lists every case
}
}
sealed interface Payment permits MobileMoney, BankTransfer, Cash {
long amount();
}
record MobileMoney(String phone, long amount) implements Payment {}
record BankTransfer(String bank, String reference, long amount) implements Payment {}
record Cash(long amount) implements Payment {}
record Result(String student, int score) {
Result { // compact constructor: validation
if (score < 0 || score > 100) throw new IllegalArgumentException("score " + score);
}
}
enum Grade {
A(75, "Excellent"), B(65, "Very good"), C(45, "Good"), D(30, "Satisfactory"), F(0, "Fail");
final int minScore;
final String remark;
Grade(int minScore, String remark) {
this.minScore = minScore;
this.remark = remark;
}
static Grade forScore(int score) {
for (Grade g : values()) {
if (score >= g.minScore) return g;
}
return F;
}
}Key points
- Use records for immutable data; validate in the compact constructor.
- Enums model fixed sets and can carry data and behaviour.
- Sealed types + switch patterns = the compiler guarantees every case is handled.
Exercise
Model shapes as a sealed interface with Circle, Rectangle and Triangle records, and compute the area with a pattern-matching switch. Add an enum Term { ONE, TWO } and a record TermResult(String student, Term term, int score).
Show solution
Try the exercise yourself first — then compare your approach with this one.
The sealed interface lists the three shapes, so the pattern-matching switch needs no default — add a fourth shape and this code stops compiling until you handle it. The enum and record model term results with no boilerplate.
import java.util.List;
public class SealedShapes {
sealed interface Shape permits Circle, Rectangle, Triangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
record Triangle(double base, double height) implements Shape {}
enum Term { ONE, TWO }
record TermResult(String student, Term term, int score) {}
static double area(Shape shape) {
return switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.width() * r.height();
case Triangle t -> 0.5 * t.base() * t.height();
};
}
public static void main(String[] args) {
for (Shape s : List.of(new Circle(1), new Rectangle(3, 4), new Triangle(6, 2))) {
System.out.printf("%s -> %.2f%n", s, area(s));
}
List<TermResult> results = List.of(
new TermResult("Amina", Term.ONE, 86), new TermResult("Amina", Term.TWO, 89));
for (TermResult r : results) {
System.out.println(r.student() + " term " + r.term() + ": " + r.score());
}
}
}