此外,Oracle 官方也有清单可供一览: Java Language Changes (opens new window)
当 Java 8 引入流和 Lambda 这两个重大更新时,函数式编程风格赋予了 Java 更少模板代码的语法。虽然最近的版本更新没添加这么富有影响的特性,但带来了很多较小的改进。自从 Java 切换到一个更快的发布节奏后,每六个月就会发布一个新版本。记录类可能是最近更新中最重要的一个特性,模式匹配和封闭类也会让处理纯数据更容易。
Java 17(LTS)
- switch 模式匹配(预览特性:mag:)
Java 16
Java 15
- 包含有用信息的空指针异常
Java 14
Java 11(LTS)
Java 9
- 匿名内部类的钻石操作符
- try-with-resources 语句中允许使用 effectively-final 变量
- 下划线不再是合法变量名
想要一览塑造这个新平台所有的 JEP,其涵盖了包括 API 、性能与安全方面的改进,参看这份精选清单:Java 8 以来所有的改进 (opens new window) 。
开始支持版本: JDK 17 (opens new window) ( JDK 15 (opens new window) JDK 16 (opens new window) 为预览特性)
封闭类用于限定哪些类或接口可以被用于继承或实现它们。这给设计公共 API 和替换枚举来构建固定数量的可选项,提供了一个更好的工具。
老版本的 Java 也提供了一些机制来实现类似的效果。标记为 final
的类不允许被继承,配合访问修饰符就能确保仅同一包中的类才能继承。
在此之上, 封闭类 提供了更细粒度的控制,让开发者能显式地列举其子类。
public sealed class Shape permits Circle, Quadrilateral {...}
1
2
在这个例子中,被允许继承 Shape
类的只有 Circle
和 Quadrilateral
类。实际上, permits这个关键字有些歧义,因为它不止有允许的含义,其 要求列举的类直接继承封闭类 。
此外,正如人们所期望的那样,如果 任何其它的类试图继承这个封闭类,都会出现编译错误。
继承封闭类的类需要符合一些规则。
开发者被强制每次都需要显式定义出封闭类继承的边界,通过添加任意一个下面修饰符到被允许的子类上来实现:
final sealed non-sealed
因为子类本身也可以是封闭的,这就意味着可以定义 整条继承链包含限定的可选项 :
public sealed class Shape permits Circle, Quadrilateral, WeirdShape {...} public final class Circle extends Shape {...} public sealed class Quadrilateral extends Shape permits Rectangle, Parallelogram {...} public final class Rectangle extends Quadrilateral {...} public final class Parallelogram extends Quadrilateral {...} public non-sealed class WeirdShape extends Shape {...}
1
2
3
4
5
6
7
8
9
10
11
如果这些类比较简短,且大多仅和数据相关,那么可以将它们声明在 同一个源文件中, permits
关键字也可以忽略 :
public sealed class Shape { public final class Circle extends Shape {} public sealed class Quadrilateral extends Shape { public final class Rectangle extends Quadrilateral {} public final class Parallelogram extends Quadrilateral {} } public non-sealed class WeirdShape extends Shape {} }
1
2
3
4
5
6
7
8
9
10
记录类也可以作为封闭类的子类,因为它们是隐式 final 的。
被允许继承的类必须和父类(封闭类)在同一个包里,如果是使用 java 模块,那它们必须在同一模块中。
:warning:技巧:考虑使用封闭类优于枚举
在 封闭类 出现前,只能用 枚举类 对固定可选项建模,比如:
enum Expression { ADDITION, SUBTRACTION, MULTIPLICATION, DIVISION }
1
2
3
4
5
6
然而,所有的变量都要在同一个源文件中,且 枚举类 不支持需要实例的情况(而不是常量),例如表示一个类型的单个消息。
封闭类 提供一个比 枚举类 更好的选择,使得用普通类来为固定可选项建模成为可能。当 switch 模式匹配 在生产环境可用时就更能充分发挥其作用, 封闭类 能像枚举一样在 switch
表达式中使用,编译器能自动检查代码是否涵盖了全部情况。
枚举类的值可以使用 values
方法列举出来。对应到封闭类和封闭接口,可以使用 getPermittedSubclasses
方法例举出所有被允许继承的子类。
switch 模式匹配(预览特性:mag:)
开始支持版本: JDK 17 (opens new window) 为预览特性
此前, switch
表达式的用法十分局限:条件仅仅支持完全相等的情况,而且只支持很少几类类型:数值、枚举类和字符串。
这个预览特性增强了 swith
表达式的用法, 可以用在任意的类型上,匹配更复杂的模式 。
这些新特性是 向后兼容的 , switch
搭配传统的常量就如以往一样的使用,例如和枚举值:
var symbol = switch (expression) { case ADDITION -> "+"; case SUBTRACTION -> "-"; case MULTIPLICATION -> "*"; case DIVISION -> "/"; };
1
2
3
4
5
6
然而,随着 JEP 394: Pattern Matching for instanceof (opens new window) 的引入,现在可以和类型模式搭配使用:
return switch (expression) { case Addition expr -> "+"; case Subtraction expr -> "-"; case Multiplication expr -> "*"; case Division expr -> "/"; };
1
2
3
4
5
6
模式还支持 卫语句 ,写法为 type pattern && guard expression
:
String formatted = switch (o) { case Integer i && i > 10 -> String.format("a large Integer %d", i); case Integer i -> String.format("a small Integer %d", i); default -> "something else"; };
1
2
3
4
5
这和使用 if
声明的类型模式构成了很好的对称性,因为类似的模式可以用于条件语句:
if (o instanceof Integer i && i > 10) { return String.format("a large Integer %d", i); } else if (o instanceof Integer i) { return String.format("a large Integer %d", i); } else { return "something else"; }
1
2
3
4
5
6
7
与 if
条件类似, 模式变量的作用域是分支敏感的 (flow sensitive)。比如,在下面的条件中,变量 i
的作用域为卫语句及其右边的表达式。
case Integer i && i > 10 -> String.format("a large Integer %d", i);
1
总体来说,模式匹配会按你期待的那样工作,但其中涉及了很多规则和边缘情况。如果你感兴趣,我推荐你读下相关的 JEPs 或是看下Pattern matching for instanceof章节。
Switch 现在也能匹配 null
值 。通常来说,当 null
值传给 switch
会报 NullPointerException
。当一个常量试图匹配 null
的时候也会出现这种情况。然而,现在可以显示得声明 null
在分支上:
switch (s) { case null -> System.out.println("Null"); case "Foo" -> System.out.println("Foo"); default -> System.out.println("Something else"); }
1
2
3
4
5
当 switch
表达式没有完全覆盖各种情况分支,或是一个分支条件完全包含了另一个分支,编译器会报错。
Object o = 1234; // OK String formatted = switch (o) { case Integer i && i > 10 -> String.format("a large Integer %d", i); case Integer i -> String.format("a small Integer %d", i); default -> "something else"; }; // 编译错误 - 'switch' 表达式没有涵盖所有可能的输入值 String formatted = switch (o) { case Integer i && i > 10 -> String.format("a large Integer %d", i); case Integer i -> String.format("a small Integer %d", i); }; // 编译错误 - 第二个条件已包含在第一个条件分支中 String formatted = switch (o) { case Integer i -> String.format("a small Integer %d", i); case Integer i && i > 10 -> String.format("a large Integer %d", i); default -> "something else"; };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
这个 预览 特性需要通过 --enable-preview
标记来显式开启。当然我们试目以待吧,因为更多的特性将要到来: JEP405 (opens new window) 针对 Java 18 ,旨在带来可用于解构的数组模式和记录类模式。
开始支持版本: JDK 16 (opens new window) ( JDK 14 (opens new window) JDK 15 (opens new window) 为预览特性)
<