Lesson Completion
Back to course

Sealed Classes: Controlled Inheritance Hierarchies

Beginner
12 minutes4.8Java

1. The Hook (The "Byte-Sized" Intro)

  • In a Nutshell: Sealed classes (Java 17+) = controlled inheritance—restrict which classes can extend/implement. sealed class Shape permits Circle, Square = only permitted subclasses allowed.
  • Subclasses must be: final (no further extension), sealed (continue restriction), non-sealed (open inheritance).
  • Benefits: Exhaustive switch (compiler knows all types), domain modeling (algebraic data types), API control (prevent unauthorized extensions).
  • Use for: Payment types, user roles, state machines!

Think of exclusive club. Normal class = open to public (anyone can join). Sealed class = members-only (permits list = approved members). Subclass options: final = joined but can't sponsor others, sealed = can sponsor specific people, non-sealed = can sponsor anyone. Bouncer enforces list!


2. Conceptual Clarity (The "Simple" Tier)

💡 The Analogy

  • Sealed class: VIP section of club (controlled access)
  • permits: Guest list (who's allowed in)
  • final subclass: Guest (came in, but can't bring friends)
  • sealed subclass: Member (can bring specific approved guests)
  • non-sealed: Staff (can bring anyone)

Sealed Class Hierarchy

graph TD A[sealed Shape<br/>permits Circle, Square, Triangle] --> B[final Circle] A --> C[final Square] A --> D[sealed Triangle<br/>permits RightTriangle] D --> E[final RightTriangle] style A fill:#C2185B style B fill:#2E7D32 style C fill:#2E7D32 style D fill:#C2185B style E fill:#2E7D32

3. Technical Mastery (The "Deep Dive")

Sealed Class Rules

RuleDescription
sealed modifierDeclares class sealed
permits clauseLists allowed subclasses
Same package/modulePermitted classes must be accessible
Subclass modifiersMust be final, sealed, or non-sealed

4. Interactive & Applied Code

java
// ======================================== // EXAMPLE 1: Basic Sealed Class // ======================================== sealed class Shape permits Circle, Square, Triangle {} final class Circle extends Shape { double radius; Circle(double radius) { this.radius = radius; } double area() { return Math.PI * radius * radius; } } final class Square extends Shape { double side; Square(double side) { this.side = side; } double area() { return side * side; } } final class Triangle extends Shape { double base, height; Triangle(double base, double height) { this.base = base; this.height = height; } double area() { return 0.5 * base * height; } } // ❌ COMPILE ERROR: Not in permits list // class Pentagon extends Shape {} public class SealedClassesDemo { public static void main(String[] args) { demonstrateExhaustiveSwitch(); demonstratePaymentExample(); demonstrateUserRoles(); } // Exhaustive switch (pattern matching) static void demonstrateExhaustiveSwitch() { System.out.println("=== EXHAUSTIVE SWITCH ==="); Shape shape = new Circle(5); // ✅ Compiler knows ALL possible types double area = switch (shape) { case Circle c -> c.area(); case Square s -> s.area(); case Triangle t -> t.area(); // No default needed! Compiler knows these are ALL types }; System.out.println("Area: " + area); } // Real-world: Payment types sealed interface Payment permits CreditCard, DebitCard, Cash {} record CreditCard(String number, String cvv) implements Payment {} record DebitCard(String number, String pin) implements Payment {} record Cash(double amount) implements Payment {} static void demonstratePaymentExample() { System.out.println("\n=== PAYMENT EXAMPLE ==="); Payment payment = new CreditCard("1234-5678", "123"); String message = switch (payment) { case CreditCard cc -> "Processing credit card: " + cc.number(); case DebitCard dc -> "Processing debit card: " + dc.number(); case Cash cash -> "Received cash: $" + cash.amount(); }; System.out.println(message); } // Sealed hierarchy with non-sealed sealed class UserRole permits Admin, User {} final class Admin extends UserRole { void manageUsers() { System.out.println("Managing users"); } } non-sealed class User extends UserRole { // non-sealed: Anyone can extend User now void viewContent() { System.out.println("Viewing content"); } } // ✅ OK: User is non-sealed class PremiumUser extends User { void accessPremium() { System.out.println("Accessing premium content"); } } static void demonstrateUserRoles() { System.out.println("\n=== USER ROLES ==="); UserRole role = new Admin(); switch (role) { case Admin admin -> admin.manageUsers(); case User user -> user.viewContent(); } } } // ======================================== // EXAMPLE: Result Type (Success/Failure) // ======================================== sealed interface Result<T> permits Success, Failure {} record Success<T>(T value) implements Result<T> {} record Failure<T>(String error) implements Result<T> {} class ResultDemo { public static void main(String[] args) { Result<Integer> result = divide(10, 2); String message = switch (result) { case Success<Integer> s -> "Result: " + s.value(); case Failure<Integer> f -> "Error: " + f.error(); }; System.out.println(message); } static Result<Integer> divide(int a, int b) { if (b == 0) { return new Failure<>("Division by zero"); } return new Success<>(a / b); } } // ======================================== // EXAMPLE: State Machine // ======================================== sealed interface OrderState permits Pending, Confirmed, Shipped, Delivered {} record Pending() implements OrderState {} record Confirmed(String confirmationCode) implements OrderState {} record Shipped(String trackingNumber) implements OrderState {} record Delivered(java.time.LocalDateTime deliveryTime) implements OrderState {} class Order { private OrderState state = new Pending(); void confirm(String code) { state = new Confirmed(code); } void ship(String tracking) { state = new Shipped(tracking); } String getStatus() { return switch (state) { case Pending p -> "Order pending"; case Confirmed c -> "Confirmed: " + c.confirmationCode(); case Shipped s -> "Shipped: " + s.trackingNumber(); case Delivered d -> "Delivered at: " + d.deliveryTime(); }; } }

5. The Comparison & Decision Layer

FeatureNormal ClassSealed Class
InheritanceOpen to allRestricted to permits
Exhaustivenessdefault requiredNo default needed
Domain modelingLeaky abstractionPrecise control
ExtensionUnpredictablePredictable

6. The "Interview Corner" (The Edge)

The "Killer" Interview Question: "final vs sealed vs non-sealed subclass?" Answer:

  • final: End of the line (no further inheritance)
  • sealed: Continues restriction (permits own list)
  • non-sealed: Opens inheritance (anyone can extend)
java
sealed class Animal permits Dog, Cat {} final class Dog extends Animal {} // ✅ final: Can't extend Dog sealed class Cat extends Animal permits Kitten {} // ✅ sealed: Can extend Cat, but only Kitten non-sealed class Bird extends Animal {} // ✅ non-sealed: Anyone can extend Bird class Sparrow extends Bird {} // ✅ OK: Bird is non-sealed

Pro-Tips:

  1. Sealed + Records = Algebraic Data Types:
java
sealed interface Expr permits Num, Add, Mul {} record Num(int value) implements Expr {} record Add(Expr left, Expr right) implements Expr {} record Mul(Expr left, Expr right) implements Expr {} // Exhaustive pattern matching int eval(Expr expr) { return switch (expr) { case Num(int value) -> value; case Add(var l, var r) -> eval(l) + eval(r); case Mul(var l, var r) -> eval(l) * eval(r); }; }
  1. When to use sealed:
java
// ✅ USE: Finite set of types (payment methods, states) sealed interface Payment permits Cash, Card {} // ❌ DON'T USE: Open-ended extension (plugins, frameworks) // Not sealed: class Plugin {} // Let users extend!

Topics Covered

Java Fundamentals

Tags

#java#programming#beginner-friendly

Last Updated

2025-02-01