Что такое запечатанные классы в Java 17
Сегодня я обновил свою версию java с 16 до 17 и обнаружил, что
sealed
class - это новая функция в нем. Думаю, это можно заявить так -
public sealed class Main{
}
Но какова польза от запечатанных классов в java?
Я также знал, что это функция предварительного просмотра в jdk-15.
11 ответов
Запечатанный класс или интерфейс могут быть расширены или реализованы только теми классами и интерфейсами, которым это разрешено.
Ситуация в прошлом была:
- Вы не могли ограничить интерфейс, расширяемый другим интерфейсом
- Вы не могли ограничить, какие классы могут реализовать определенный интерфейс, и запретить всем другим классам возможность реализовать определенный интерфейс.
- Вы должны были объявить класс как final, чтобы не быть расширенным другим классом. Однако таким образом ни один класс не может расширить объявленный окончательный класс.
Сейчас ситуация такова:
Вы можете ограничить расширение интерфейса другим интерфейсом. Теперь вы можете выбрать только определенные интерфейсы, которым разрешено расширять выбранный интерфейс.
Пример:
public sealed interface MotherInterface permits ChildInterfacePermitted {} //Has to be declared either as sealed or non-sealed public non-sealed interface ChildInterfacePermitted extends MotherInterface {} public interface AnotherChildInterface extends MotherInterface {} //compiler error! It is not included in the permits of mother inteface
Теперь вы можете создать интерфейс и выбрать только определенные классы, которым разрешено реализовать этот интерфейс. Всем остальным классам это не разрешено.
Пример:
public sealed interface MotherInterface permits ImplementationClass1 {} Hsd to be declared either as final or as sealed or as non-sealed public final class ImplementationClass1 implements MotherInterface {} public class ImplementationClass2 implements MotherInterface {} //compiler error! It is not included in the permits of mother inteface
Теперь вы можете ограничить расширяемый класс (как и раньше с final), но теперь вы можете разрешить некоторым конкретным классам расширять его. Итак, теперь у вас больше контроля, так как раньше ключевое слово final было абсолютным ограничением каждого класса от расширения объявленного класса final.
Пример:
public sealed class MotherClass permits ChildClass1 {} Has to be declared either as final or as sealed or as non-sealed public non-sealed class ChildClass1 extends MotherClass {} public class ChildClass2 extends MotherClass {} //compiler error! It is not included in the permits of MotherClass
Важные заметки:
Запечатанный класс и его разрешенные подклассы должны принадлежать одному модулю и, если он объявлен в безымянном модуле, одному и тому же пакету.
Пример:
Допустим, у нас есть такой же безымянный модуль и следующие пакеты
-packageA -Implementationclass1.java -packageB -MotherClass.java
или
-root -MotherClass.java -packageA -Implementationclass1.java
Вы получите сообщение об ошибке. Класс не может расширять запечатанный класс из другого пакета . Поэтому, если у вас есть безымянный модуль, все участвующие классы и интерфейсы для запечатанной функции должны быть размещены точно в одном пакете.
Каждый разрешенный подкласс должен напрямую расширять закрытый класс.
Вы можете просмотреть примеры по этой ссылке.
Короче говоря, запечатанные классы дают вам контроль над тем, какие модели, классы и т. Д. Могут реализовывать или расширять этот класс / интерфейс.
Пример по ссылке:
public sealed interface Service permits Car, Truck {
int getMaxServiceIntervalInMonths();
default int getMaxDistanceBetweenServicesInKilometers() {
return 100000;
}
}
Этот интерфейс позволяет реализовать его только в автомобилях и грузовиках.
Запечатанные классы
Запечатанный класс — это ограничение, позволяющее реализовать его только заданным классам. Эти разрешенные классы должны явно расширять запечатанный класс, а также иметь один изsealed
,non-sealed
, илиfinal
модификаторы. Эта функция появилась в версии java 17 (JEP 409) и была доступна в виде предварительной версии раньше (Java 15).
sealed interface IdentificationDocument permits IdCard, Passport, DrivingLicence { }
final class IdCard implements IdentificationDocument { }
final class Passport implements IdentificationDocument { }
non-sealed class DrivingLicence implements IdentificationDocument { }
class InternationalDrivingPermit extends DrivingLicence {}
Использование с сопоставлением с образцом
Я нахожу эту новую функцию потрясающей в сочетании с сопоставлением с образцом, представленным в качестве предварительного просмотра в Java 17 (JEP 406)!
Разрешенное ограничение класса гарантирует, что все подклассы известны во время компиляции. Используяswitch
выражений ( JEP 361 начиная с Java 14) компилятор требует либо перечислить все разрешенные классы, либо использовать ключевое слово для оставшихся. Рассмотрим следующий пример, используя приведенные выше классы:
final String code = switch(identificationDocument) {
case IdCard idCard -> "I";
case Passport passport -> "P";
};
Компилятор послеjavac Application.java --enable-preview -source 17
приводит к ошибке:
Application.java:9: error: the switch expression does not cover all possible input values final String code = switch(identificationDocument) { ^ Note: Application.java uses preview features of Java SE 17. Note: Recompile with -Xlint:preview for details. 1 error
Как только все разрешенные классы илиdefault
ключевое слово используется, компиляция прошла успешно:
final String code = switch(identificationDocument) {
case IdCard idCard -> "I";
case Passport passport -> "P";
case DrivingLicence drivingLicence -> "D";
};
Закрытые классы — это дополнение к языку Java, дающее автору класса детальный контроль над тем, какие классы могут его расширять. Раньше вы могли либо разрешить всем наследовать ваш класс, либо полностью запретить его (используя «final»). Это также работает для интерфейсов.
Кроме того, это обязательное условие для функции сопоставления с образцом, поскольку все потомки известны во время компиляции.
Как обычно, есть и обратная сторона — запечатанные классы и интерфейсы нельзя сымитировать/подделать, что является препятствием для тестирования.
Запечатанный класс или интерфейс могут быть расширены или реализованы только теми классами и интерфейсами, которым это разрешено.
Запечатанные классы
- Запечатанный класс - это класс или интерфейс, который ограничивает, какие другие классы или интерфейсы могут его расширять.
- Запечатанные классы и интерфейсы представляют собой ограниченные иерархии классов, которые обеспечивают больший контроль над наследованием.
- Все прямые подклассы запечатанного класса известны во время компиляции. Никакие другие подклассы не могут появиться после компиляции модуля с запечатанным классом.
- Например, сторонние клиенты не могут расширить ваш запечатанный класс в своем коде. Таким образом, каждый экземпляр запечатанного класса имеет тип из ограниченного набора, который известен при компиляции этого класса.
Определение запечатанных классов
Чтобы запечатать класс, добавьте к его объявлению модификатор sealed. Затем после любых предложений extends и Implements добавьте пункт разрешений. В этом пункте указаны классы, которые могут расширять запечатанный класс.
Например, следующее объявление Shape определяет три разрешенных подкласса: Circle, Square и Rectangle:
public sealed class Shape
permits Circle, Square, Rectangle {
}
Определите следующие три разрешенных подкласса, Circle, Square и Rectangle, в том же модуле или в том же пакете, что и герметичный класс:
public final class Circle extends Shape {
public float radius;
}
public non-sealed class Square extends Shape {
public double side;
}
public sealed class Rectangle extends Shape permits FilledRectangle {
public double length, width;
}
У Rectangle есть еще один подкласс, FilledRectangle:
public final class FilledRectangle extends Rectangle {
public int red, green, blue;
}
Ограничения на разрешенные подклассы
Они должны быть доступны запечатанному классу во время компиляции.
Например, чтобы скомпилировать
Shape.java
, компилятор должен иметь доступ ко всем разрешенным классам Shape:Circle.java
,Square.java
, а такжеRectangle.java
. Кроме того, поскольку Rectangle - это запечатанный класс, компилятору также необходим доступ кFilledRectangle.java
.Они должны напрямую расширять закрытый класс.
У них должен быть ровно один из следующих модификаторов, чтобы описать, как он продолжает запечатывание, инициированное его суперклассом:
- final : не может быть продлен дальше
- sealed : может быть расширен только разрешенными подклассами
- незапечатанный : может быть расширен неизвестными подклассами; запечатанный класс не может помешать его разрешенным подклассам делать это
Например, разрешенные подклассы Shape демонстрируют каждый из этих трех модификаторов:
Circle
окончательно покаRectangle
запечатан иSquare
не запечатан.Они должны находиться в том же модуле, что и запечатанный класс (если запечатанный класс находится в названном модуле) или в том же пакете (если запечатанный класс находится в безымянном модуле, как в примере Shape.java).
Например, в следующем объявлении
com.example.graphics.Shape
, это
permitted subclasses
все в разных упаковках. Этот пример будет компилироваться, только если
Shape
и все его разрешенные подклассы находятся в одноименном модуле.
package com.example.graphics;
public sealed class Shape
permits com.example.polar.Circle,
com.example.quad.Rectangle,
com.example.quad.simple.Square { }
Что такое запечатанные классы в Java?
Модификатор можно считать сильной формой запечатывания, при которой расширение/реализация полностью запрещены .
Концептуально: = + пустое предложение.
В отличие отfinal
который полностью запрещает расширение/реализация. АSealed
class
/interface
ограничить, какие другие классы или интерфейсы могут расширять или реализовывать их.
История
- Запечатанные классы были предложены JEP 360 и представлены в JDK 15 в качестве предварительной функции.
- Они были снова предложены с уточнениями в JEP 397 и представлены в JDK 16 в качестве предварительной функции.
- В этом JEP предлагается завершить работу над запечатанными классами в JDK 17 без изменений по сравнению с JDK 16.
Цели
Позвольте автору класса или интерфейса контролировать, какой код отвечает за его реализацию.
Предоставьте более декларативный способ ограничения использования суперкласса, чем модификаторы доступа.
Описание
Класс/интерфейс запечатывается применением
sealed
модификатор его объявления.Затем, после любых предложений расширения и реализации,
permits
Предложение определяет классы, которым разрешено расширять запечатанный класс.Пример
Например, следующее объявление
Loan
указывает разрешенноеUnsecuredLoan
,SecuredLoan
подклассы:sealed interface Loan permits UnsecuredLoan,SecuredLoan{} final class UnsecuredLoan implements Loan {} record SecuredLoan() implements Loan{}
Преимущества запечатанного класса с сопоставлением с образцом
Используя сопоставление шаблонов, вместо проверки экземпляра запечатанного класса с помощью цепочек if-else мы можем использовать расширенные шаблоны тестирования типов.
Это позволит компилятору Java проверить, что все разрешенные классы для нас покрыты.
Например, рассмотрим этот код:
void checkLoanType(Loan loan) {
if (loan instanceof UnsecuredLoan unsecuredLoan) {
// something
} else if (loan instanceof SecuredLoan securedLoan) {
// something
}
}
Компилятор Java не может гарантировать, что тесты экземпляра охватывают все разрешенные подклассы Loan.Таким образом, сообщение об ошибке во время компиляции не будет выдано, если какой-либо экземпляр Loan будет опущен.
Напротив, использование сопоставления с образцомswitch
выражения, компилятор может подтвердить, что охвачен каждый разрешенный подкласс Loan. Более того, компилятор выдаст сообщение об ошибке, если какой-либо из случаев пропущен :
void checkLoanType(Loan loan) {
switch (loan) {
case SecuredLoan securedLoan -> {} //generated by compiler.
case UnsecuredLoan unsecuredLoan -> {} //generated by compiler.
}
}
Согласно этой документации , запечатанные классы и интерфейсы ограничивают, какие другие классы или интерфейсы могут их расширять или реализовывать. Это скорее декларативный способ ограничения использования суперкласса, а не использование модификаторов доступа.
В Java класс может быть окончательным, поэтому никакие другие классы не могут наследовать его. Если класс не является окончательным, он открыт для всех других классов для поддержки повторного использования кода. Это может вызвать проблемы моделирования данных.
Приведенный ниже класс NumberSystem открыт для всех классов, поэтому любой подкласс может его расширить. Что, если вы хотите ограничить эту NumberSystem фиксированным набором подклассов (двоичный, десятичный, восьмеричный и шестнадцатеричный)?. Это означает, что вы не хотите, чтобы какой-либо другой произвольный класс расширял этот класс NumberSystem.
class NumberSystem { ... }
final class Binary extends NumberSystem { ... }
final class Decimal extends NumberSystem { ... }
final class Octal extends NumberSystem { ... }
final class HexaDecimal extends NumberSystem { ... }
Используя запечатанный класс, вы можете достичь этого, управляя подклассами, которые могут его расширять, и предотвращать это от любого другого произвольного класса.
Все
sealed
Java-классы или интерфейсы должны использовать
permits
ключевое слово. Например:
Родительский.класс:
public sealed class Parent permits Child1, Child2 {
void parentMethod() {
System.out.println("from a sealed parent class ");
}
}
Ребенок1.java:
public final class Child1 extends Parent {
public static void main(String[] args) {
Child1 obj = new Child1();
obj.parentMethod();
}
}
Ребенок2.java:
public final class Child2 extends Parent {
public static void main(String[] args) {
Child2 obj = new Child2();
obj.parentMethod();
}
}
Ребенок3.java
public final class Child3 extends Parent {
public static void main(String[] args) {
Child3 obj = new Child3();
obj.parentMethod();
}
}
Этот
Child3
код класса вызовет ошибку времени компиляции, в которой говорится, что тип Child3, расширяющий запечатанный класс Parent, должен быть разрешенным подтипом Parent (
permits Child3
, как
Child1
а также
Child2
).
Запечатанные классы. Запечатанные классы — это функция, которая позволяет разработчикам контролировать степень, в которой другие классы могут наследовать от них. Объявляя класс как запечатанный, вы можете указать, каким другим классам разрешено создавать его подклассы. Эта функция улучшает инкапсуляцию и обеспечивает больший контроль над иерархиями и расширениями классов. https://java-speed.blogspot.com/2023/07/what-is-sealed-classes-in-java-17.html
Вот краткое двухминутное видео-объяснение Что такое запечатанный класс в Java 17
=====================================================================
Запечатанные классы и интерфейсы ограничивают, какие другие классы или интерфейсы могут их расширять или реализовывать.
Цель:
- Авторы класса контролируют, какой код отвечает или разрешен для его реализации.
- Он предоставляет более декларативный способ ограничения использования суперкласса.