Java 17 新特性之密封类型
工作中所有项目都已升级到了当前的 LTS 版 Java 21, 眼看 Java 快要来到了下一个 LTS 版本 - Java 25,将于今年 9 月份发布。四年前记录过一篇 Java 10 ~ 16 一路向前冲(新特性一箩筐),其中夹杂着孵化,预览中的以及正式的特性。现在继续跟随着 Java 16 之后版本的特性,主要讲述正式的,可直观体验到的特性,孵化与预览中的特性只会简单提及。
先还是看下 Java 的发布日期安排 Oracle Java SE Support Roadmap
为了迎接两个月后的 Java 25(LTS) 版本,开始回顾总结在 Java 17 与 Java 24 之间的关键新特性。又要用到 IntelliJ IDEA 中显示的不同版本 Java 的 Language level 的一句话描述
转换成文字描述
探索 Java 17 的新特性依然由官方的 What's New in JDK 17 - New Features and Enhancements 开始
密封类型的用意大约是要创建一个基类以及有限的已知子类。在没有密封类型的情况下,只能用 final 去修饰子类型,例如
由于作为 SDK 使用, 所以 Shape, Circle, 和 Square 都需要声明为 public, 虽然 Circle 和 Square 是 final, 但仍然无法阻止由 Shape 衍生出其他的子类。有了 sealed 关键字的话就可以改为
这样就能彻底杜绝再创建新的 Shpe, Circle, 或 Square 的子类了。
密封类涉及到
Java 在防止创建子类的方式还有一些,如 final 类型完全杜绝子类,package-private 构造函数只允许同包中创建子类(但会影响外部访问基类信息),private 构造函数比 final 类还严格些。
Sealed 类型有些像枚举,只不过后者限定的是有限的实例,sealed 要达成的目的是有限的子类。
首先我们只引用
IntelliJ IDEA 会有
又会看到对
这时候对
用
用
如果少量的实现代码可写在一个源文件中,如
如果希望 Shape 为抽象类,就加上
我们看到上面两段代码的错误都是
以 Maven 项目为例,具体做法是创建下面的文件布局
module-info.java 中内容为
命令模块名为
Shape.java 代码
Circle.java 代码
这样编译就没问题了,想要使用 Circle 类,可创建 src/main/java/com/example/Client.java
正常使用模块 xyz 中的 Circle 类。
由于 record 天生是 final, 所以实现 Shape 的 Triangle 不用再写 final。
正如模式对于枚举类型在编译期就能推断出有没有覆盖所有的选项,没有则必需要有
测试下面的代码,String 为非 Sealed Class, Shape 为 Sealed Class
执行的输出为
它封装了 JVM class 文件中所有合法的常量描述类型,对于我们编写代码好像没什么用。
Sealed Class 对我们以后使用框架编写应用似乎没多大用处,理解它将有助于我们读懂第三方库使用了 sealed 特性的代码,或者自己要写 SDK 时又不希望使用者任意发挥时有些帮助。
通过写本文顺便把自 Java 9 开始引入的模块概念也略微有些体验。本想一文综述 Java 17 的特别关键新特性,没想第一个 Sealed Class 就占满了篇幅,所以不得已到此为止。 永久链接 https://yanbin.blog/java-17-new-features-sealed-classes/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
先还是看下 Java 的发布日期安排 Oracle Java SE Support Roadmap
版本 发布日 原定支持至 延期支持至也就是每两年(9月份)会有一个 LTS 版本,中间若干过度版本。在正式项目中尽可能只用 LTS 版本,因为 LTS 版更稳定,且有长期的补丁,不能项目进行中不得不在进行非 LTS 主版本升级。
Java 17(LTS) 2021/9 2026/9 2029/12
Java 18-20 2022/3 - 2023/3 2022/9 - 2023/9 N/A
Java 21(LTS) 2023/9 2028/9 2031/9
Java 22 2024/3 2024/9 N/A
Java 23 2024/9 2025/3 N/A
Java 24 2025/3 2025/9 N/A
Java 25(LTS) 2025/9 2030/9 2033/9
为了迎接两个月后的 Java 25(LTS) 版本,开始回顾总结在 Java 17 与 Java 24 之间的关键新特性。又要用到 IntelliJ IDEA 中显示的不同版本 Java 的 Language level 的一句话描述
![]() |
转换成文字描述
- 15 - Text blocks
- 16 - Records, patterns, local enums and interfaces
- 17 - Sealed types, always-strict floating-point semantics
- 18 - JavaDoc snippets
- 19, 20 - No new language features
- 21 - Record patterns, pattern matching for switch, (Preview) - String templates, unnamed classes and instance main methods, etc.
- 22 - Unnamed variables and patterns
- 23 - Markdown documentation comments, (Preview) - Primitive types in patterns, implicitly declared classes, etc.
- 24 - Stream gatherers, (Preview) - Flexible constructor bodies, simple sources files, etc.
探索 Java 17 的新特性依然由官方的 What's New in JDK 17 - New Features and Enhancements 开始
JEP 409: 密封类型(Sealed classes)
在 Kotlin, Scala 和 C# 都有 sealed 类型。Java 17 的 Sealed 类型有以下几个特性- sealed 可用于修饰的类或接口
- sealed 声明的类或接口,必须有子类型
- sealed 类型的子类型必须使用 non-sealed, final, 或 sealed 修饰(non-sealed 子类可衍生孙子类, final 则不可再派生, sealed 则进一步要求明确的子类)
- sealed 类型用 permits 指定的子类型的当然是已定义的
密封类型的用意大约是要创建一个基类以及有限的已知子类。在没有密封类型的情况下,只能用 final 去修饰子类型,例如
1// Shape.java
2public abstract Shape {}
3
4// Circle.java
5public final class Circle extends Shape {}
6
7// Square.java
8public final class Square extends Shape {}由于作为 SDK 使用, 所以 Shape, Circle, 和 Square 都需要声明为 public, 虽然 Circle 和 Square 是 final, 但仍然无法阻止由 Shape 衍生出其他的子类。有了 sealed 关键字的话就可以改为
1// Shape.java
2public sealed class Shape permits Circle, Square {}
3
4// Circle.java
5public final class Circle extends Shape {}
6
7// Square.java
8public final class Square extends Shape {}这样就能彻底杜绝再创建新的 Shpe, Circle, 或 Square 的子类了。
密封类涉及到
sealed, non-sealed, permits, 以及已有的 final 关键字。Java 在防止创建子类的方式还有一些,如 final 类型完全杜绝子类,package-private 构造函数只允许同包中创建子类(但会影响外部访问基类信息),private 构造函数比 final 类还严格些。
Sealed 类型有些像枚举,只不过后者限定的是有限的实例,sealed 要达成的目的是有限的子类。
逐步理解密封类型
我们可通过在 IntelliJ IDEA 中的提示错误来感受 Sealed Classes 的用法首先我们只引用
sealed 关键字,只写出下面的代码1public sealed class Shape {}IntelliJ IDEA 会有
Shape 的错误提示Sealed class must have subclasses这里所说的
Make 'Shape' non-sealed
non-sealed 其实是说移除 sealed 关键字,而非真把 sealed 换成 non-sealed,如此写成1public non-sealed class Shape {}又会看到对
non-sealed 的错误提示Modifier 'non-sealed' is not allowed on classes that do not have a sealed superclass所以必须要给它声明一个 subclass
1public sealed class Shape {
2}
3
4class Rectangle extends Shape {}这时候对
Rectangle 有错误提示Modifier 'sealed', 'non-sealed' or 'final' expected相应的有以下三种选择
1final class Rectangle extends Shape {}
2non-sealed class Rectangle extends Shape {}
3sealed class Rectangle extends Shape {}用
final 表示 Rectangle 不可被继承non-sealed 让 Rectangle 成为一个普通,可被任意继承的类,重新开放,如由它创建一个 Square 的子类sealed 又让 Rectangle 又像之前的 Shape 那样要求有子类的编译错误Sealed class must have subclasses这时候还没有引入
permits 关键字,其实下面两种写法是等效的 | |
用
permits 关键字在基类 Shape 这一层就明确了允许的子类,而不需要到子类中寻找。建议不要省略 permits 关键字。如果少量的实现代码可写在一个源文件中,如
1public abstract sealed class Shape {
2 final class Rectangle extends Shape {}
3 final class Circle extends Shape {}
4}如果希望 Shape 为抽象类,就加上
abstract 关键字Sealed Class 与包,模块
sealed 类与 permits 的子类必须在同一模块(named module) 或相同的包( package - unnamed module)。比如我们在不同的包中分别创建 Shape 和 Circle 类1package a;
2
3public abstract sealed class Shape permits b.Circle{}1package b;
2
3public final class Circle extends a.Shape {}我们看到上面两段代码的错误都是
Class 'b.Circle' from another package not allowed to extend sealed class 'a.Shape' in unnamed module要么放在同一个包中(unnamed module), 如果我们创建一个 named module, Shape 和 Circle 就能在不同的 package 了。
以 Maven 项目为例,具体做法是创建下面的文件布局
1.
2├── pom.xml
3└── src
4 └── main
5 └── java
6 ├── module-info.java
7 └── xyz
8 ├── a
9 │ └── Shape.java
10 └── b
11 └── Circle.javamodule-info.java 中内容为
1module xyz {
2}命令模块名为
xyzShape.java 代码
1package xyz.a;
2
3import xyz.b.Circle;
4
5public abstract sealed class Shape permits Circle {
6}Circle.java 代码
1package xyz.b;
2
3import xyz.a.Shape;
4
5public final class Circle extends Shape {
6}这样编译就没问题了,想要使用 Circle 类,可创建 src/main/java/com/example/Client.java
1package com.example;
2
3import xyz.b.Circle;
4
5public class Client {
6 public static void main(String[] args) {
7 System.out.println(new Circle());
8 }
9}正常使用模块 xyz 中的 Circle 类。
sealed 关键也可用于 interface,只需要注意 interface 与 class 的区别就是了,例如下面代码演示了 sealed interface 的用法1sealed interface Shape permits Circle, Rectangle, Triangle {}
2final class Circle implements Shape {}
3non-sealed class Rectangle implements Shape {}
4final class Square extends Rectangle {}
5record Triangle(float x, float y, float z) implements Shape {}由于 record 天生是 final, 所以实现 Shape 的 Triangle 不用再写 final。
Sealed Class 与模式匹配
这要穿越到 Java 21,因为 Java 21 才开始正式支持模式匹配。Sealed Class 像枚举一样有着限定数目的类型,所以针对前面的代码,我们可以写下面 switch-case1String matchName(Shape shape) {
2 return switch (shape) {
3 case Circle c -> "Circle";
4 case Square s -> "Square";
5 case Rectangle r -> "Rectangle";
6 };
7}正如模式对于枚举类型在编译期就能推断出有没有覆盖所有的选项,没有则必需要有
default 分支,对于 Sealed Class 也一样,编译期也知道所有的子类型,所以这段代码覆盖了所有的分支,无须 default, 可通过编译。Sealed Class 与字节码,反射
Sealed Class 编译产生的字节码中有一个 PermittedSubclasses 表示了它是不是一个 Sealed Class, 有值(非空数组)则是, null 值则不是。测试下面的代码,String 为非 Sealed Class, Shape 为 Sealed Class
1System.out.println(String.class.getPermittedSubclasses());
2System.out.println(String.class.isSealed());
3System.out.println(Shape.class.getPermittedSubclasses());
4System.out.println(Shape.class.isSealed());执行的输出为
null
false
[Ljava.lang.Class;@2e0fa5d3
true
JDK 中的 Sealed class
在 java.lang.constant 包中 1package java.lang.constant;
2
3public sealed interface ConstantDesc
4 permits ClassDesc,
5 MethodHandleDesc,
6 MethodTypeDesc,
7 Double,
8 DynamicConstantDesc,
9 Float,
10 Integer,
11 Long,
12 String {
13......
14}它封装了 JVM class 文件中所有合法的常量描述类型,对于我们编写代码好像没什么用。
小结 Sealed class 的几条约束
基本是从 JEP 409 中转述的内容- sealed 类与 permits 允许的子类必须在同一个模块中(named module), 或在同一个包中(unnamed module)
- 每一个 permits 子类必须直接继承自 sealed 类
- 每个 permits 子类必须使用
final,sealed和non-sealed中的一个关键字修饰final修饰的让该子类不再被继承(record 暗含着 final)sealed让子类又成为一个 sealed class, 将适用一样的 sealed 类规则non-sealed又让子类复原为普通的类,即解除了sealed的限制。就像没有用sealed修饰的类一样(class Person),它将不在对任何未知子类对它的继承。
Sealed Class 对我们以后使用框架编写应用似乎没多大用处,理解它将有助于我们读懂第三方库使用了 sealed 特性的代码,或者自己要写 SDK 时又不希望使用者任意发挥时有些帮助。
通过写本文顺便把自 Java 9 开始引入的模块概念也略微有些体验。本想一文综述 Java 17 的特别关键新特性,没想第一个 Sealed Class 就占满了篇幅,所以不得已到此为止。 永久链接 https://yanbin.blog/java-17-new-features-sealed-classes/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
