Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Sealed Class в Java
Sealed class (запечатанный класс) — это класс, который ограничивает, какие другие классы могут его расширять. Введён в Java 17 как preview feature, и стал обычной feature в Java 21.
Основная идея
Sealed class позволяет разработчику явно указать, какие классы могут наследоваться, что даёт больше контроля над иерархией наследования:
// До Java 17 — любой может расширить класс
public class Animal {
}
public class Dog extends Animal {} // OK
public class Cat extends Animal {} // OK
public class UnicornAnimal extends Animal {} // Тоже OK, но нежелательно!
// С Sealed class — контроль над наследованием
public sealed class Animal permits Dog, Cat {
}
public final class Dog extends Animal {} // OK
public final class Cat extends Animal {} // OK
public class UnicornAnimal extends Animal {} // Ошибка компиляции!
Синтаксис Sealed Class
1. Базовый синтаксис
public sealed class Shape permits Circle, Rectangle, Triangle {
public abstract double getArea();
}
// Разрешённые подклассы
public final class Circle extends Shape {
private double radius;
@Override
public double getArea() {
return Math.PI * radius * radius;
}
}
public final class Rectangle extends Shape {
private double width, height;
@Override
public double getArea() {
return width * height;
}
}
public final class Triangle extends Shape {
private double a, b, c;
@Override
public double getArea() {
// Формула Герона
double s = (a + b + c) / 2;
return Math.sqrt(s * (s - a) * (s - b) * (s - c));
}
}
2. Не-final подклассы
Подкласс не обязан быть final, но может быть sealed сам:
public sealed class Vehicle permits Car, Truck, Bicycle {
}
// Может быть final
public final class Car extends Vehicle {
}
// Может быть sealed (промежуточный уровень)
public sealed class Truck extends Vehicle permits SportsTruck, DeliveryTruck {
}
public final class SportsTruck extends Truck {
}
public final class DeliveryTruck extends Truck {
}
// Может быть non-sealed
public non-sealed class Bicycle extends Vehicle {
// Её можно расширять без ограничений
}
public class MountainBike extends Bicycle {} // OK
Для чего нужны Sealed Classes
1. Контроль над иерархией наследования
Запечатанные классы позволяют разработчику явно указать, какие расширения допустимы:
public sealed class PaymentMethod permits CreditCard, DebitCard, PayPal {
public abstract void process(BigDecimal amount);
}
// Только эти три способа оплаты разрешены
public final class CreditCard extends PaymentMethod {
@Override
public void process(BigDecimal amount) { /* ... */ }
}
public final class DebitCard extends PaymentMethod {
@Override
public void process(BigDecimal amount) { /* ... */ }
}
public final class PayPal extends PaymentMethod {
@Override
public void process(BigDecimal amount) { /* ... */ }
}
// Это будет ошибкой компиляции
// public class CryptoCurrency extends PaymentMethod { }
2. Лучшая оптимизация компилятором
Компилятор знает все возможные подклассы, поэтому может сделать лучшие оптимизации:
public sealed class Status permits Active, Inactive, Pending {
}
// Компилятор знает, что это все возможные значения
Status status = getStatus();
if (status instanceof Active) {
// Активирована работа
} else if (status instanceof Inactive) {
// Деактивирована
} else if (status instanceof Pending) {
// В ожидании
}
// Компилятор может предупредить, если забыли случай
3. Pattern Matching с Sealed Classes
Sealed classes отлично работают с pattern matching в switch:
public sealed interface HttpResponse permits SuccessResponse, ErrorResponse {}
public final class SuccessResponse implements HttpResponse {
private int statusCode;
private String body;
public SuccessResponse(int statusCode, String body) {
this.statusCode = statusCode;
this.body = body;
}
public int getStatusCode() { return statusCode; }
public String getBody() { return body; }
}
public final class ErrorResponse implements HttpResponse {
private int statusCode;
private String error;
public ErrorResponse(int statusCode, String error) {
this.statusCode = statusCode;
this.error = error;
}
public int getStatusCode() { return statusCode; }
public String getError() { return error; }
}
// Pattern matching в Java 21+
HttpResponse response = getResponse();
String result = switch (response) {
case SuccessResponse(int code, String body) when code == 200 ->
"Success: " + body;
case SuccessResponse(int code, String body) ->
"OK (" + code + "): " + body;
case ErrorResponse(int code, String error) ->
"Error (" + code + "): " + error;
};
4. Sealed Records
Sealed часто используется с records для создания алгебраических типов данных:
public sealed interface Result<T> permits Success, Failure {}
public record Success<T>(T value) implements Result<T> {}
public record Failure(String error) implements Result<Object> {}
// Использование
Result<Integer> result = calculateResult();
String message = switch (result) {
case Success(var value) -> "Result: " + value;
case Failure(var error) -> "Error: " + error;
};
5. Безопасность и документирование
Sealed class документирует намерение разработчика и предотвращает непредусмотренное расширение:
// Без sealed
public abstract class DatabaseConnection {
public abstract void connect();
public abstract void disconnect();
}
// Кто-то может создать DatabaseConnection и использовать неправильно
// С sealed
public sealed abstract class DatabaseConnection
permits PostgresConnection, MySqlConnection, OracleConnection {
public abstract void connect();
public abstract void disconnect();
}
// Ясно, что только эти три БД поддерживаются
Практический пример: HTTP API Response
public sealed interface ApiResponse permits SuccessApiResponse, ErrorApiResponse {
}
public record SuccessApiResponse<T>(
int statusCode,
T data,
String message
) implements ApiResponse {}
public record ErrorApiResponse(
int statusCode,
String error,
String message
) implements ApiResponse {}
@RestController
@RequestMapping("/api")
public class UserController {
@GetMapping("/users/{id}")
public ResponseEntity<?> getUser(@PathVariable Long id) {
try {
User user = userService.getUser(id)
.orElseThrow(() -> new UserNotFoundException(id));
return ResponseEntity.ok(
new SuccessApiResponse<>(200, user, "User found")
);
} catch (UserNotFoundException e) {
return ResponseEntity.notFound().build();
}
}
}
// Обработка ответа
ApiResponse response = getApiResponse();
String message = switch (response) {
case SuccessApiResponse(int code, var data, var msg) ->
msg + ": " + data;
case ErrorApiResponse(int code, var error, var msg) ->
error + " (" + code + ")";
};
Ограничения Sealed Classes
- Все разрешённые подклассы должны быть в той же пакете
- Подклассы должны быть явно указаны в permits
- Подклассы должны быть либо final, либо sealed, либо non-sealed
// Ошибка — подклассы в разных пакетах (в Java 17-20)
public sealed class Animal permits com.zoo.Dog { }
// В Java 21+ можно указать другие пакеты
Сравнение с final
// final — запрещает любое наследование
public final class Immutable {
}
// sealed — разрешает наследование только для конкретных классов
public sealed class Controlled permits AllowedSubclass {
}
Когда использовать Sealed Classes
- Явное ограничение иерархии наследования
- ADT (Algebraic Data Types) с records
- Работа с pattern matching
- API дизайн, где нужен контроль расширяемости
- Классификация типов (статусы, виды платежей и т.д.)
Резюме
Sealed class используется для:
- Контроля над иерархией наследования
- Документирования допустимых подклассов
- Поддержки pattern matching
- Лучшей оптимизации компилятором
- Повышения безопасности и надёжности кода
- Создания ADT с records