[英]What are sealed classes in Java 17?
今天,我将我的 Java 版本从 16 更新到 17,我发现sealed
类是其中的一个新特性。 我认为可以这样声明:
public sealed class Main permits AClass, AnotherClass {
}
但是,Java 中的密封类有什么用?
我也知道这是 jdk-15 中的预览功能。
简而言之,密封类使您可以控制哪些模型、类等可以实现或扩展该类/接口。
来自链接的示例:
public sealed interface Service permits Car, Truck {
int getMaxServiceIntervalInMonths();
default int getMaxDistanceBetweenServicesInKilometers() {
return 100000;
}
}
该接口只允许 Car 和 Truck 实现。
JEP 409将其解释为
密封类或接口只能由允许这样做的类和接口进行扩展或实现。
更实际的解释如下:
过去的情况是:
现在的情况是:
您现在可以限制由其他接口扩展的接口,并为仅允许扩展它的某些特定接口制定规则。
例子:
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 {} //Has 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
您将收到错误Class is not allowed to extend seal class from another package 。 因此,如果您有一个未命名的模块,则密封函数的所有参与类和接口必须完全放在同一个包中。
每个允许的子类必须直接扩展密封类。
根据本文档,密封类和接口限制了哪些其他类或接口可以扩展或实现它们。 它更像是一种限制超类使用的声明方式,而不是使用访问修饰符。
在 Java 中,一个类可以是 final 的,因此没有其他类可以继承它。 如果一个类不是最终的,那么它对所有其他类开放以支持代码可重用性。 这样做会引起数据建模问题。
下面的 NumberSystem 类对所有类开放,因此任何子类都可以扩展它。 如果您想将此 NumberSystem 限制为一组固定的子类(Binary、Decimal、Octal 和 HexaDecimal)怎么办? 这意味着您不希望任何其他任意类扩展此 NumberSystem 类。
class NumberSystem { ... }
final class Binary extends NumberSystem { ... }
final class Decimal extends NumberSystem { ... }
final class Octal extends NumberSystem { ... }
final class HexaDecimal extends NumberSystem { ... }
使用密封类,您可以通过控制可以扩展它的子类来实现它,并防止任何其他任意类这样做。
所有密封的 java 类或接口都必须使用 permit 关键字,这里是示例。
父类
公共密封类父许可 Child1,Child2 {
void parentMethod() {
System.out.println("from a sealed parent class ");
}
}
Child1.java
公共最终类 Child1 扩展父 {
public static void main(String[] args) {
Child1 obj = new Child1();
obj.parentMethod();
}
}
Child2.java
公共最终类 Child2 扩展 Parent{
public static void main(String[] args) {
Child2 obj = new Child2();
obj.parentMethod();
}
}
Child3.java
公共最终类 Child3 扩展 Parent{
public static void main(String[] args) {
Child3 obj = new Child3();
obj.parentMethod();
}
}
此 Child 3 类代码将引发编译时错误,指出扩展密封类 Parent 的类型 Child3 应该是 Parent 的允许子类型(允许 Child3 像 child 1 和 child2)
密封类
密封的 class 是一个仅允许给定类实现它的约束。 这些允许的类必须明确扩展密封的 class 并且还具有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 ) 开始,我发现这个新功能与作为预览引入的模式匹配相结合非常棒!
允许的 class 限制确保所有子类在编译时都是已知的。 使用switch
表达式(从 Java 14 开始的JEP 361 )编译器需要列出所有允许的类或对其余类使用default
关键字。 考虑使用上述类的以下示例:
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";
};
密封类
定义密封类
要密封一个类,在它的声明中添加sealed 修饰符。 然后,在任何 extends 和 implements 子句之后,添加 permit 子句。 该子句指定了可以扩展密封类的类。
例如,以下 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
。
他们必须直接扩展密封类。
它们必须恰好具有以下修饰符之一来描述它如何继续由其超类启动的密封:
例如,Shape 的允许子类演示了这三个修饰符中的每一个: Circle
是最终的,而Rectangle
是密封的,而 The 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 { }
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.