Вопросы 231-240 - Sealed-классы и pattern matching
Ответ: Sealed (запечатанный) класс или интерфейс явно перечисляет, какие классы могут от него наследоваться. Появились в Java 17 (в 15 и 16 были в режиме preview). Это золотая середина между открытым наследованием и final.
public sealed interface Shape permits Circle, Square, Rectangle { }
public record Circle(double radius) implements Shape { }
public record Square(double side) implements Shape { }
public record Rectangle(double w, double h) implements Shape { }
// class Triangle implements Shape { } // ОШИБКА: не входит в permits
switch можно проверять на полнотуОтвет:
permitsfinal, sealed или non-sealedfinal, поэтому модификатор не нуженpermits можно опустить — компилятор выведет их сам
sealed interface Vehicle permits Car, Truck, Bike { }
final class Car implements Vehicle { } // дальше нельзя
sealed class Truck implements Vehicle permits Pickup { } // продолжает ограничение
final class Pickup extends Truck { }
non-sealed class Bike implements Vehicle { } // снова открыт для наследования
Ответ:
| Модификатор | Что означает |
|---|---|
final |
Иерархия здесь заканчивается, наследников нет |
sealed |
Наследники есть, но только из своего списка permits |
non-sealed |
Снимает ограничение: класс снова открыт для любого наследования |
non-sealed — осознанный выход из «замкнутой» иерархии. Компилятор по-прежнему будет считать набор прямых наследников sealed-родителя полным, но ниже этого класса подтипов может быть сколько угодно.
Ответ:
| Конструкция | Особенность |
|---|---|
final класс |
Наследников нет вообще |
| Обычный (abstract) класс | Наследовать может кто угодно, набор подтипов неизвестен |
enum |
Фиксированный набор экземпляров одного типа, у всех одинаковая структура |
sealed |
Фиксированный набор типов, и у каждого могут быть свои поля и состояние |
Пример: результат операции — либо успех со значением, либо ошибка с сообщением. Это разные по структуре типы, поэтому здесь подходит sealed, а не enum.
sealed interface Result permits Ok, Fail { }
record Ok(String value) implements Result { }
record Fail(int code, String message) implements Result { }
Ответ: Начиная с Java 16, после проверки instanceof можно сразу объявить переменную нужного типа без явного приведения.
// раньше
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.length());
}
// сейчас
if (obj instanceof String s) {
System.out.println(s.length());
}
// с условием
if (obj instanceof String s && s.length() > 5) {
System.out.println(s.toUpperCase());
}
// отрицание: переменная доступна после if
if (!(obj instanceof String s)) {
return;
}
System.out.println(s.length());
s определяется потоком выполнения (flow scoping): она доступна только там, где компилятор уверен, что проверка прошла успешно.
Ответ: В Java 21 switch может сопоставлять значение не только с константами, но и с типами (шаблонами), в том числе с дополнительным условием when.
static String describe(Object o) {
return switch (o) {
case null -> "null";
case Integer i when i > 100 -> "большое число " + i;
case Integer i -> "число " + i;
case String s -> "строка длиной " + s.length();
case int[] arr -> "массив из " + arr.length;
default -> "другое: " + o;
};
}
dominated)case null обрабатывает null явно; без него switch по-прежнему бросает NullPointerExceptiondefault, либо охвачены все вариантыОтвет: Если тип sealed, компилятор знает все варианты и проверяет, что switch покрывает каждый из них. Ветка default не нужна — и это хорошо: при добавлении нового наследника код перестанет компилироваться, пока вы не обработаете его.
sealed interface Shape permits Circle, Square, Rectangle { }
record Circle(double r) implements Shape { }
record Square(double side) implements Shape { }
record Rectangle(double w, double h) implements Shape { }
static double area(Shape s) {
return switch (s) {
case Circle c -> Math.PI * c.r() * c.r();
case Square sq -> sq.side() * sq.side();
case Rectangle r -> r.w() * r.h();
// default не нужен
};
}
Triangle в permits, метод area выдаст ошибку компиляции: the switch expression does not cover all possible input values. С default эта ошибка была бы скрыта.
Ответ: Связка «sealed-интерфейс + record-варианты» даёт алгебраические типы данных, а record patterns позволяют сразу доставать компоненты в switch (Java 21).
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 { }
static int eval(Expr e) {
return switch (e) {
case Num(int v) -> v;
case Add(Expr l, Expr r) -> eval(l) + eval(r);
case Mul(Expr l, Expr r) -> eval(l) * eval(r);
};
}
eval(new Add(new Num(2), new Mul(new Num(3), new Num(4)))); // 14
Ответ: В Class добавлены методы для работы с sealed-иерархиями (Java 17):
Shape.class.isSealed(); // true
for (Class<?> c : Shape.class.getPermittedSubclasses()) {
System.out.println(c.getSimpleName());
}
// Circle
// Square
// Rectangle
permits проверяется при загрузке класса JVM, а не только компилятором, поэтому подменить иерархию через байткод не получитсяgetPermittedSubclasses() вернёт nullProxy.newProxyInstance() не создаёт прокси для sealed-интерфейсаОтвет:
switchenum — если у вариантов одинаковая структура| Возможность | Финальная версия |
|---|---|
switch-выражения |
Java 14 |
Pattern matching для instanceof |
Java 16 |
| Records | Java 16 |
| Sealed-классы | Java 17 |
Pattern matching для switch и record patterns |
Java 21 |