Sealed Classes
Sau bài này, bạn sẽ:
- Hiểu Sealed Classes (Java 17+) — kiểm soát chặt chẽ phân cấp kế thừa (inheritance hierarchy)
- Biết cách dùng sealed, permits, non-sealed keywords
- Nắm được 3 options cho subclasses: final, sealed, non-sealed
- Kết hợp sealed classes với pattern matching cho kiểm tra đầy đủ (exhaustive switch)
- Áp dụng sealed classes cho domain modeling và type-safe hierarchies
Bài trước: Records — Đã học về immutable data carriers. Bài này sẽ tìm hiểu Sealed Classes — kiểm soát ai được phép extend class của bạn.
Giới thiệu
Sealed classes được giới thiệu như preview feature trong Java 15, và trở thành standard feature trong Java 17 (September 2021). Sealed classes cho phép bạn kiểm soát chặt chẽ phân cấp kế thừa (inheritance hierarchy) — xác định class/interface nào được phép extend hoặc implement.
Hành trình của Sealed Classes
| Version | Status | JEP |
|---|---|---|
| Java 15 | Preview | JEP 360 |
| Java 16 | Second Preview | JEP 397 |
| Java 17 | Standard | JEP 409 |
| Java 21 | Kết hợp Pattern Matching for switch | JEP 441 |
Sealed classes mất 3 phiên bản từ preview đến standard — cho thấy Java Team cẩn trọng trong thiết kế.
Vấn đề Sealed Classes giải quyết
Trước Sealed Classes
Với class/interface thông thường, bạn chỉ có 2 lựa chọn:
// Option 1: public class - ANYONE có thể extend
public class Shape {
// ...
}
// Bất kỳ ai cũng có thể:
class Triangle extends Shape {}
class Pentagon extends Shape {}
// Không kiểm soát được!
// Option 2: final class - KHÔNG AI được extend
public final class Circle {
// ...
}
// Không thể extend, mất tính linh hoạt
Vấn đề: Không có cách nào để nói "chỉ cho phép 3 subclasses này, không cho phép thêm nữa".
Với Sealed Classes
// Chỉ cho phép Circle, Rectangle, Square extend
public sealed class Shape
permits Circle, Rectangle, Square {
// ...
}
// OK - Được liệt kê trong permits
final class Circle extends Shape {}
final class Rectangle extends Shape {}
final class Square extends Shape {}
// COMPILE ERROR - Không được phép
class Pentagon extends Shape {} // ERROR!
Cú pháp Sealed Classes
sealed và permits keywords
// Sealed class
public sealed class Shape
permits Circle, Rectangle, Square {
public abstract double area();
}
// Permitted subclasses
final class Circle extends Shape {
private final double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
final class Rectangle extends Shape {
private final double width, height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public double area() {
return width * height;
}
}
final class Square extends Shape {
private final double side;
public Square(double side) {
this.side = side;
}
@Override
public double area() {
return side * side;
}
}
Subclass options: final, sealed, non-sealed
Mỗi permitted subclass PHẢI chọn 1 trong 3 options:
| Modifier | Ý nghĩa | Use case |
|---|---|---|
| final | Không thể extend thêm | Leaf class trong hierarchy |
| sealed | Có thể extend, nhưng kiểm soát | Intermediate class trong hierarchy |
| non-sealed | Mở cho inheritance bình thường | Cho phép extension tự do |
public sealed class Vehicle
permits Car, Truck, Motorcycle {
// ...
}
// Option 1: final - Không thể extend
final class Car extends Vehicle {
// Không ai extend Car được
}
// Option 2: sealed - Kiểm soát tiếp
sealed class Truck extends Vehicle
permits PickupTruck, SemiTruck {
// Chỉ PickupTruck và SemiTruck extend Truck được
}
// Option 3: non-sealed - Mở lại
non-sealed class Motorcycle extends Vehicle {
// Bất kỳ ai cũng extend Motorcycle được
}
// Subclasses của Truck
final class PickupTruck extends Truck {}
final class SemiTruck extends Truck {}
// OK - Motorcycle là non-sealed
class Scooter extends Motorcycle {}
class SportBike extends Motorcycle {}
- final: Khi không cần extend thêm (phổ biến nhất)
- sealed: Khi cần phân cấp nhiều level
- non-sealed: Khi muốn mở lại inheritance từ điểm này
Sealed Interfaces
Interfaces cũng có thể sealed:
public sealed interface Expr
permits ConstantExpr, PlusExpr, TimesExpr, NegExpr {
int eval();
}
// Permitted implementations
record ConstantExpr(int value) implements Expr {
@Override
public int eval() {
return value;
}
}
record PlusExpr(Expr left, Expr right) implements Expr {
@Override
public int eval() {
return left.eval() + right.eval();
}
}
record TimesExpr(Expr left, Expr right) implements Expr {
@Override
public int eval() {
return left.eval() * right.eval();
}
}
record NegExpr(Expr expr) implements Expr {
@Override
public int eval() {
return -expr.eval();
}
}
// Usage
Expr expr = new PlusExpr(
new ConstantExpr(5),
new TimesExpr(
new ConstantExpr(3),
new ConstantExpr(2)
)
);
System.out.println(expr.eval()); // 5 + (3 * 2) = 11
Kiểm tra đầy đủ (Exhaustiveness Checking) trong Switch
Một trong những lợi ích lớn nhất của sealed classes là kiểm tra đầy đủ (exhaustiveness checking) trong switch expressions (Java 17+):
Without Sealed Classes
// Class bình thường - cần default case
public double calculateArea(Shape shape) {
return switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.width() * r.height();
// PHẢI có default vì có thể có subclass khác
default -> throw new IllegalArgumentException("Unknown shape");
};
}
With Sealed Classes
// Sealed class - KHÔNG cần default case
public sealed interface Shape permits Circle, Rectangle, Square {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
record Square(double side) implements Shape {}
public double calculateArea(Shape shape) {
// Compiler biết chỉ có 3 cases, không cần default!
return switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.width() * r.height();
case Square s -> s.side() * s.side();
// Không cần default!
};
}
// Nếu thêm Triangle vào permits, compiler sẽ BẮT phải thêm case Triangle
Compiler đảm bảo bạn xử lý TẤT CẢ cases. Nếu thêm permitted subclass mới, tất cả switch expressions sẽ compile error cho đến khi bạn thêm case mới.
Sealed Classes + Records = Kiểu dữ liệu đại số
Kết hợp sealed classes với records tạo nên kiểu dữ liệu đại số (Algebraic Data Types - ADTs) như trong Scala, Haskell:
Algebraic Data Type Pattern với Sealed + Records
Ví dụ: Result type (thay Optional)
public sealed interface Result<T>
permits Success, Failure {
// Static factory methods
static <T> Result<T> success(T value) {
return new Success<>(value);
}
static <T> Result<T> failure(String error) {
return new Failure<>(error);
}
}
record Success<T>(T value) implements Result<T> {}
record Failure<T>(String error) implements Result<T> {}
// Usage
public Result<User> findUser(long id) {
User user = repository.findById(id);
if (user != null) {
return Result.success(user);
} else {
return Result.failure("User not found: " + id);
}
}
// Pattern matching
Result<User> result = findUser(123);
String message = switch (result) {
case Success<User> s -> "Found user: " + s.value().getName();
case Failure<User> f -> "Error: " + f.error();
};
Ví dụ: JSON values
public sealed interface JsonValue
permits JsonObject, JsonArray, JsonString,
JsonNumber, JsonBoolean, JsonNull {
}
record JsonObject(Map<String, JsonValue> fields) implements JsonValue {}
record JsonArray(List<JsonValue> elements) implements JsonValue {}
record JsonString(String value) implements JsonValue {}
record JsonNumber(double value) implements JsonValue {}
record JsonBoolean(boolean value) implements JsonValue {}
record JsonNull() implements JsonValue {}
// Parser
public String toStringValue(JsonValue json) {
return switch (json) {
case JsonString s -> s.value();
case JsonNumber n -> String.valueOf(n.value());
case JsonBoolean b -> String.valueOf(b.value());
case JsonNull n -> "null";
case JsonObject o -> o.fields().toString();
case JsonArray a -> a.elements().toString();
};
}
So sánh với ngôn ngữ khác
Java sealed + records đạt được hiệu quả tương đương các ngôn ngữ khác:
// Kotlin sealed class
sealed class Result<out T> {
data class Success<T>(val value: T) : Result<T>()
data class Failure(val error: String) : Result<Nothing>()
}
// Rust enum (discriminated union)
enum Result<T> {
Success(T),
Failure(String),
}
// Java sealed interface + records
sealed interface Result<T> permits Success, Failure {}
record Success<T>(T value) implements Result<T> {}
record Failure<T>(String error) implements Result<T> {}
Ba cách viết trên biểu đạt cùng một ý tưởng: kiểu hợp phân biệt (discriminated union) — một giá trị chỉ có thể thuộc một trong các biến thể đã định trước.
Ví dụ: Shape Hierarchy
Traditional approach (không sealed)
public abstract class Shape {
public abstract double area();
public abstract double perimeter();
}
class Circle extends Shape {
private final double radius;
public Circle(double radius) { this.radius = radius; }
@Override
public double area() {
return Math.PI * radius * radius;
}
@Override
public double perimeter() {
return 2 * Math.PI * radius;
}
}
// Vấn đề: Bất kỳ ai cũng tạo subclass mới
class Ellipse extends Shape { ... } // Uncontrolled!
Sealed approach
public sealed interface Shape
permits Circle, Rectangle, Triangle {
double area();
double perimeter();
}
record Circle(double radius) implements Shape {
@Override
public double area() {
return Math.PI * radius * radius;
}
@Override
public double perimeter() {
return 2 * Math.PI * radius;
}
}
record Rectangle(double width, double height) implements Shape {
@Override
public double area() {
return width * height;
}
@Override
public double perimeter() {
return 2 * (width + height);
}
}
record Triangle(double a, double b, double c) implements Shape {
@Override
public double area() {
double s = (a + b + c) / 2;
return Math.sqrt(s * (s - a) * (s - b) * (s - c));
}
@Override
public double perimeter() {
return a + b + c;
}
}
### Sự kết hợp hoàn hảo: Sealed + Records + Pattern Matching
Sealed classes kết hợp với records và pattern matching tạo nên một hệ sinh thái hoàn chỉnh:
```mermaid
flowchart TD
S["sealed interface Shape<br/>permits Circle, Rectangle, Triangle"]
S --> R1["record Circle(double radius)"]
S --> R2["record Rectangle(double w, double h)"]
S --> R3["record Triangle(double a, double b, double c)"]
R1 --> PM["Pattern Matching Switch<br/>(exhaustive — không cần default)"]
R2 --> PM
R3 --> PM
PM --> D1["case Circle(var r) → π×r²"]
PM --> D2["case Rectangle(var w, var h) → w×h"]
PM --> D3["case Triangle(var a, var b, var c) → Heron"]
style S fill:#e1f5fe,stroke:#0288d1
style R1 fill:#e8f5e9,stroke:#388e3c
style R2 fill:#e8f5e9,stroke:#388e3c
style R3 fill:#e8f5e9,stroke:#388e3c
style PM fill:#fff3e0,stroke:#f57c00
style D1 fill:#fce4ec,stroke:#c62828
style D2 fill:#fce4ec,stroke:#c62828
style D3 fill:#fce4ec,stroke:#c62828
// Usage với exhaustive switch
public void printShapeInfo(Shape shape) {
String description = switch (shape) {
case Circle c ->
"Circle with radius " + c.radius();
case Rectangle r ->
"Rectangle " + r.width() + "x" + r.height();
case Triangle t ->
"Triangle with sides " + t.a() + ", " + t.b() + ", " + t.c();
};
System.out.println(description);
System.out.println("Area: " + shape.area());
System.out.println("Perimeter: " + shape.perimeter());
}
Khi nào dùng Sealed Classes
1. Domain Modeling
// Payment methods
public sealed interface PaymentMethod
permits CreditCard, DebitCard, PayPal, BankTransfer {
}
record CreditCard(String number, String cvv, String expiry)
implements PaymentMethod {}
record DebitCard(String number, String pin)
implements PaymentMethod {}
record PayPal(String email)
implements PaymentMethod {}
record BankTransfer(String accountNumber, String routingNumber)
implements PaymentMethod {}
// Process payment với exhaustive checking
public void processPayment(PaymentMethod method, double amount) {
switch (method) {
case CreditCard cc -> processCreditCard(cc, amount);
case DebitCard dc -> processDebitCard(dc, amount);
case PayPal pp -> processPayPal(pp, amount);
case BankTransfer bt -> processBankTransfer(bt, amount);
}
}
2. State Machines
public sealed interface OrderState
permits Pending, Confirmed, Shipped, Delivered, Cancelled {
}
record Pending(LocalDateTime createdAt) implements OrderState {}
record Confirmed(LocalDateTime confirmedAt, String paymentId)
implements OrderState {}
record Shipped(LocalDateTime shippedAt, String trackingNumber)
implements OrderState {}
record Delivered(LocalDateTime deliveredAt, String signature)
implements OrderState {}
record Cancelled(LocalDateTime cancelledAt, String reason)
implements OrderState {}
// State transitions
public OrderState processEvent(OrderState current, OrderEvent event) {
return switch (current) {
case Pending p -> switch (event) {
case PaymentReceived pr ->
new Confirmed(LocalDateTime.now(), pr.paymentId());
case OrderCancelled oc ->
new Cancelled(LocalDateTime.now(), oc.reason());
default -> current;
};
case Confirmed c -> switch (event) {
case OrderShipped os ->
new Shipped(LocalDateTime.now(), os.trackingNumber());
default -> current;
};
// ... other states
};
}
3. AST (Abstract Syntax Trees)
// Expression AST
public sealed interface Expr
permits Literal, Variable, BinaryOp, UnaryOp {
}
record Literal(Object value) implements Expr {}
record Variable(String name) implements Expr {}
record BinaryOp(String operator, Expr left, Expr right) implements Expr {}
record UnaryOp(String operator, Expr operand) implements Expr {}
// Evaluator
public Object eval(Expr expr, Map<String, Object> env) {
return switch (expr) {
case Literal lit -> lit.value();
case Variable var -> env.get(var.name());
case BinaryOp op -> evalBinaryOp(op, env);
case UnaryOp op -> evalUnaryOp(op, env);
};
}
4. API Responses/Events
// API Response types
public sealed interface ApiResponse
permits SuccessResponse, ErrorResponse, ValidationErrorResponse {
}
record SuccessResponse(Object data, String message)
implements ApiResponse {}
record ErrorResponse(int code, String message)
implements ApiResponse {}
record ValidationErrorResponse(Map<String, List<String>> errors)
implements ApiResponse {}
// HTTP status code mapping
public int getStatusCode(ApiResponse response) {
return switch (response) {
case SuccessResponse s -> 200;
case ErrorResponse e -> e.code();
case ValidationErrorResponse v -> 400;
};
}
5. Xử lý lỗi có cấu trúc (Structured Error Handling)
sealed interface AppError permits ValidationError, NotFoundError,
AuthError, InternalError {}
record ValidationError(String field, String message) implements AppError {}
record NotFoundError(String resource, String id) implements AppError {}
record AuthError(String reason) implements AppError {}
record InternalError(Exception cause) implements AppError {}
// Xử lý lỗi thống nhất
public ResponseEntity<?> handleError(AppError error) {
return switch (error) {
case ValidationError(var field, var msg) ->
ResponseEntity.badRequest()
.body(Map.of("field", field, "error", msg));
case NotFoundError(var resource, var id) ->
ResponseEntity.status(404)
.body(Map.of("message", resource + " not found: " + id));
case AuthError(var reason) ->
ResponseEntity.status(401)
.body(Map.of("error", reason));
case InternalError(var cause) -> {
log.error("Internal error", cause);
yield ResponseEntity.status(500)
.body(Map.of("error", "Internal server error"));
}
};
}
Mẫu này an toàn hơn exception-based error handling vì compiler bắt buộc xử lý mọi trường hợp lỗi.
Same Package Requirement
Sealed classes và permitted subclasses PHẢI ở cùng package (hoặc cùng module):
// package com.example.shapes;
public sealed class Shape
permits Circle, Rectangle { // Phải cùng package
// ...
}
// OK - Cùng package
// package com.example.shapes;
final class Circle extends Shape { ... }
// COMPILE ERROR - Khác package
// package com.example.other;
final class Triangle extends Shape { ... } // ERROR!
Nếu sealed class và permitted subclasses cùng file, có thể bỏ permits:
// Shape.java
public sealed class Shape { // Không cần permits
// ...
}
// Cùng file
final class Circle extends Shape {}
final class Rectangle extends Shape {}
Sealed Classes và Modules
Sealed classes có quy tắc nghiêm ngặt về vị trí:
Unnamed modules (không dùng module-info.java)
Permitted subclasses PHẢI ở cùng package:
// ✅ Cùng package com.example.model
package com.example.model;
public sealed class Shape permits Circle, Rectangle {}
final class Circle extends Shape {} // OK - cùng package
final class Rectangle extends Shape {} // OK - cùng package
// ❌ Khác package → Compile Error
package com.example.other;
final class Triangle extends Shape {} // ERROR!
Named modules (dùng module-info.java)
Permitted subclasses PHẢI ở cùng module (có thể khác package):
// module-info.java
module com.example.shapes {
exports com.example.shapes.api;
exports com.example.shapes.impl;
}
// ✅ Cùng module, khác package
package com.example.shapes.api;
public sealed class Shape
permits com.example.shapes.impl.Circle,
com.example.shapes.impl.Rectangle {}
package com.example.shapes.impl;
public final class Circle extends Shape {} // OK - cùng module
public final class Rectangle extends Shape {} // OK - cùng module
Tại sao? Sealed classes đảm bảo compile-time exhaustiveness. Nếu subclasses ở module khác, compiler không thể verify tất cả subclasses tại compile time.
Sealed Classes thay thế Visitor Pattern
Trước sealed classes, Visitor Pattern là cách phổ biến để thêm operations cho class hierarchy mà không sửa các class gốc. Sealed classes + pattern matching thay thế hoàn toàn:
Truyền thống: Visitor Pattern
// Visitor Pattern - Verbose, boilerplate nhiều
interface ShapeVisitor<R> {
R visitCircle(Circle circle);
R visitRectangle(Rectangle rectangle);
R visitTriangle(Triangle triangle);
}
abstract class Shape {
abstract <R> R accept(ShapeVisitor<R> visitor);
}
class Circle extends Shape {
double radius;
@Override
<R> R accept(ShapeVisitor<R> visitor) {
return visitor.visitCircle(this);
}
}
// ... Rectangle, Triangle tương tự
// Mỗi operation = 1 Visitor implementation
class AreaCalculator implements ShapeVisitor<Double> {
@Override
public Double visitCircle(Circle c) { return Math.PI * c.radius * c.radius; }
@Override
public Double visitRectangle(Rectangle r) { return r.width * r.height; }
@Override
public Double visitTriangle(Triangle t) { /* ... */ return 0.0; }
}
Modern: Sealed + Pattern Matching
// Sealed + Pattern matching — Clean, readable
sealed interface Shape permits Circle, Rectangle, Triangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
record Triangle(double a, double b, double c) implements Shape {}
// Mỗi operation = 1 method với switch
double area(Shape shape) {
return switch (shape) {
case Circle(var r) -> Math.PI * r * r;
case Rectangle(var w, var h) -> w * h;
case Triangle(var a, var b, var c) -> {
double s = (a + b + c) / 2;
yield Math.sqrt(s * (s - a) * (s - b) * (s - c));
}
};
}
String describe(Shape shape) {
return switch (shape) {
case Circle(var r) -> "Hình tròn bán kính " + r;
case Rectangle(var w, var h) -> "HCN " + w + "×" + h;
case Triangle(var a, var b, var c) -> "Tam giác " + a + ", " + b + ", " + c;
};
}
| Tiêu chí | Visitor Pattern | Sealed + Pattern Matching |
|---|---|---|
| Boilerplate | Rất nhiều (accept, visit methods) | Rất ít |
| Thêm operation mới | Thêm Visitor mới | Thêm method với switch |
| Thêm subtype mới | Sửa tất cả Visitors | Compiler báo lỗi tất cả switch chưa exhaustive |
| Type safety | Runtime (có thể quên visit method) | Compile-time (exhaustiveness) |
| Readability | Khó theo dõi flow | Dễ đọc, sequential |
Sealed Classes vs Enums
| Đặc điểm | Sealed Classes | Enums |
|---|---|---|
| Values | Unlimited instances | Fixed constants |
| Data | Mỗi subclass có fields riêng | Tất cả constants cùng fields |
| Methods | Mỗi subclass có methods riêng | Shared methods |
| Polymorphism | Full OOP | Limited |
| Extensibility | Compile-time controlled | Không extend được |
// ENUM - Fixed constants
enum PaymentMethod {
CREDIT_CARD, DEBIT_CARD, PAYPAL;
// Tất cả constants giống nhau
}
// SEALED CLASS - Flexible
sealed interface PaymentMethod
permits CreditCard, DebitCard, PayPal {}
record CreditCard(String number, String cvv) implements PaymentMethod {}
record DebitCard(String number, String pin) implements PaymentMethod {}
record PayPal(String email) implements PaymentMethod {}
// Mỗi implementation có structure riêng
Khi nào dùng Sealed Class, Enum, hay Abstract Class?
Sealed Abstract Classes
public sealed abstract class Command
permits CreateCommand, UpdateCommand, DeleteCommand {
protected final long entityId;
protected Command(long entityId) {
this.entityId = entityId;
}
public abstract void execute();
}
final class CreateCommand extends Command {
private final Map<String, Object> data;
public CreateCommand(long entityId, Map<String, Object> data) {
super(entityId);
this.data = data;
}
@Override
public void execute() {
System.out.println("Creating entity " + entityId + " with " + data);
}
}
final class UpdateCommand extends Command {
private final Map<String, Object> updates;
public UpdateCommand(long entityId, Map<String, Object> updates) {
super(entityId);
this.updates = updates;
}
@Override
public void execute() {
System.out.println("Updating entity " + entityId + " with " + updates);
}
}
final class DeleteCommand extends Command {
public DeleteCommand(long entityId) {
super(entityId);
}
@Override
public void execute() {
System.out.println("Deleting entity " + entityId);
}
}
Performance
Sealed classes không có runtime overhead. Compiler và JVM có thể optimize tốt hơn vì biết tất cả possible subclasses:
- Devirtualization: JIT compiler có thể inline method calls
- Memory layout: Better cache locality
- Switch statements: Optimized dispatch
Bên trong hoạt động ra sao
permits trong bytecode
Khi compiler xử lý sealed class, nó thêm thuộc tính PermittedSubclasses vào bytecode:
// javap -verbose Shape.class (simplified)
public sealed class Shape
PermittedSubclasses: Circle, Rectangle, Square
JVM sử dụng thuộc tính này tại class loading time để kiểm tra: khi một class cố gắng extend Shape, class loader xác nhận class đó có trong danh sách permitted hay không.
Cơ chế kiểm tra đầy đủ (Exhaustiveness)
Compiler kiểm tra exhaustiveness theo thuật toán:
- Lấy danh sách
PermittedSubclassestừ sealed type - Với mỗi
finalsubclass → phải có case tương ứng - Với mỗi
sealedsubclass → đệ quy kiểm tra các permitted subclass con - Với
non-sealedsubclass → KHÔNG THỂ kiểm tra đầy đủ → cầndefault
sealed interface A permits B, C {}
sealed interface B extends A permits D, E {}
final class C implements A {}
final class D implements B {}
final class E implements B {}
// Compiler biết exhaustive cases là: C, D, E (không phải A, B)
String result = switch (a) {
case C c -> "C";
case D d -> "D";
case E e -> "E";
// Không cần default!
};
- Permitted subclass PHẢI ở cùng module (hoặc cùng package nếu unnamed module) với sealed class
- Mỗi permitted subclass PHẢI chọn đúng 1 trong 3:
final,sealed, hoặcnon-sealed non-sealedcho phép mở rộng tiếp — phá vỡ exhaustiveness checking (switch cầndefault)- Nếu sealed class và tất cả subclass cùng file → có thể bỏ
permitsclause - Sealed interface cho phép cả
classvàrecordimplement - Record implement sealed interface → ngầm hiểu là
final(không cần viếtfinal record) - Switch exhaustiveness chỉ hoạt động khi TẤT CẢ lá trong cây kế thừa là
finalhoặcsealed
Khi một permitted subclass là non-sealed, bất kỳ ai cũng có thể extend nó. Compiler KHÔNG THỂ kiểm tra đầy đủ → switch BẮT BUỘC cần default:
sealed interface Animal permits Dog, Cat {}
final class Dog implements Animal {}
non-sealed class Cat implements Animal {}
class PersianCat extends Cat {} // OK vì Cat là non-sealed
String sound(Animal a) {
return switch (a) {
case Dog d -> "Woof";
case Cat c -> "Meow";
// ❌ Compile Error nếu không có default!
// Vì PersianCat (hoặc bất kỳ subclass nào của Cat)
// có thể không match "case Cat"
// ✅ Phải thêm:
default -> "Unknown";
};
}
Nhưng nếu tất cả subclasses là final hoặc sealed → KHÔNG cần default.
Record implicitly final — không cần (và không được) viết final record:
sealed interface Shape permits Circle, Rectangle {}
record Circle(double radius) implements Shape {} // ✅ implicitly final
final record Rectangle(double w, double h) implements Shape {} // ❌ Compile Error!
// "Modifier 'final' not allowed here"
Exam hay hỏi: "Record nào compile?" — nhớ record đã final, không cần viết thêm.
- Sealed class khai báo permits clause liệt kê tất cả permitted direct subclasses
- Mỗi permitted subclass phải extend/implement sealed type trực tiếp
- Mỗi permitted subclass phải là
final,sealed, hoặcnon-sealed - Permitted subclasses phải accessible tại compile time (cùng module hoặc cùng package)
- JVM enforce sealed constraint tại class loading time qua
PermittedSubclassesattribute
Tham khảo: JLS §8.1.1.2
Kết luận
Khi nào dùng Sealed Classes
✅ NÊN dùng khi:
- Muốn kiểm soát chặt chẽ phân cấp kế thừa (inheritance hierarchy)
- Domain có fixed set of types (payment methods, states, shapes, etc.)
- Cần kiểm tra đầy đủ (exhaustiveness checking) trong switch
- Modeling kiểu dữ liệu đại số (algebraic data types)
❌ KHÔNG nên dùng khi:
- Cần extensibility - third parties có thể extend
- Không biết trước tất cả subtypes
- Simple enums đã đủ
Bài tập thực hành
Bài 1: Order Status
Tạo sealed interface OrderStatus với các states:
Draft(có draft version)Submitted(có submission time)Processing(có processor ID)Completed(có completion time và result)Failed(có failure reason)
Viết method getStatusMessage(OrderStatus status) sử dụng switch expression.
Bài 2: Expression Evaluator
Tạo sealed interface MathExpr với:
Constant(double value)Add(MathExpr left, MathExpr right)Multiply(MathExpr left, MathExpr right)Negate(MathExpr expr)
Viết method double evaluate(MathExpr expr) để tính giá trị.
Bài 3: Event System
Tạo sealed interface UserEvent với:
UserRegistered(String userId, LocalDateTime timestamp)UserLoggedIn(String userId, String ipAddress, LocalDateTime timestamp)UserLoggedOut(String userId, LocalDateTime timestamp)UserDeleted(String userId, String reason, LocalDateTime timestamp)
Viết method void handleEvent(UserEvent event) xử lý các events.