从 Java 12 到 17 的新特性
Java 11(迄今为止的最后一个长期支持版本)三年后,Java 17 LTS 将于 2021 年 9 月发布。是时候快速浏览一下开发人员从 11 升级到 17 后可以享受的新功能了。请注意,在幕后进行了更多改进。本文重点介绍大多数开发人员可以直接使用的功能:
- 开关表达式 ( JEP 361 )
- 文本块 ( JEP 378 )
- 封装工具 ( JEP 392 )
- instanceof 的模式匹配(JEP 394)
- 记录 ( JEP 395 )
- 密封类 ( JEP 409 )
开关表达式
switch 现在可以返回一个值,就像一个表达式:
// 将给定 planet 的 group 分配给一个变量
String group = switch(planet){
case MERCURY, VENUS, EARTH, MARS -> "内行星";
case JUPITER, SATURN, URANUS, NEPTUNE -> "外行星";
}
如果单个 case 的右侧需要更多代码,则可以将其写入块中,并使用yield
以下方法返回值:
// 打印给定 planet 的 group,以及更多信息
// 并将给定 planet 的 group 分配给一个变量
String group = switch(planet){
case EARTH, MARS -> {
System.out.println("内行星");
System.out.println("主要由岩石组成");
yield "内部的";
}
case JUPITER, SATURN -> {
System.out.println("外行星");
System.out.println("主要由气体组成");
yield "外部的";
}
}
但是,使用新的箭头标签进行切换不需要返回值,就像 void 表达式一样:
// 打印给定 planet 的 group
// 不返回任何东西
switch(planet){
case EARTH, MARS -> System.out.println("内行星");
case JUPITER, SATURN -> System.out.println("外行星");
}
与传统的 switch 相比,新的 switch 表达式
- 使用“->”代替“:”
- 每个案例允许多个常量
- 没有贯穿语义(即,不需要中断)
- 使在 case 分支中定义的变量本地化到这个分支
此外,编译器保证了 switch 的详尽性,因为只有一种情况被执行,这意味着要么
- 所有可能的值都被列为案例(如上面的枚举由八个行星组成)
- 必须提供“默认”分支
文本块
文本块允许编写包含双引号的多行字符串,而无需使用\n
或\"
转义序列:
String block = """
可以输入多行文本内容
可以缩进
可以带有“双引号”!
"""
文本块由三个双引号"""
和一个换行符打开,并由三个双引号关闭。
Java 编译器应用智能算法从结果字符串中去除前导空格,使得
- 删除了仅与更好的 Java 源代码可读性相关的缩进
- 与字符串本身相关的缩进保持不变
在上面的示例中,结果字符串如下所示,其中每个都.
标记一个空格:
..可以输入多行文本内容
....可以缩进
......可以带有“双引号”!
想象一个跨越文本块高度的垂直条,从左到右移动并删除空格,直到它接触到第一个非空格字符。结束文本块分隔符也很重要,因此将其向左移动两个位置。
String block = """
可以输入多行文本内容
可以缩进
可以带有“双引号”!
"""
结果在以下字符串中:
可以输入多行文本内容
..可以缩进
....可以带有“双引号”!
此外,每一行的尾随空格都会被删除,这可以通过使用新的转义序列来防止\s
。
文本块内的换行符可以转义:
String block = """
请 \
不要 \
插队 \
, \
谢谢 \
"""
结果在以下字符串中,没有任何换行符:
请.不要.插队.,.谢谢
或者,也可以通过将结束定界符直接附加到字符串的末尾来删除最后的换行符
String block = """
没有最终的断线
在这个字符串的末端"""
将变量插入文本块可以像往常一样使用静态方法String::format
或新的实例方法String::formatted
来完成,写起来要短一些:
String block ="""
%s 标记位置.
""".formatted("x");
打包工具
假设您demo.jar
在lib
目录中有一个 JAR 文件,以及其他依赖项 JAR。以下命令
jpackage --name demo --input lib --main-jar demo.jar --main-class demo.Main
将此演示应用程序打包为与您当前平台相对应的本机格式:
- Linux:deb 或 rpm
- Windows:msi 或 exe
- macOS:pkg 或 dmg
生成的包还包含运行应用程序所需的 JDK 部分以及本机启动器。这意味着用户可以以特定于平台的标准方式安装、运行和卸载应用程序,而无需事先明确安装 Java。
不支持交叉编译:如果需要 Windows 用户的包,必须在 Windows 机器上用 jpackage 创建。
可以使用更多选项自定义包创建,这些选项记录在jpackage 手册页上。
Instanceof 的模式匹配
模式匹配instanceof
消除了在类型比较后执行强制转换的样板代码:
Object o = "字符串伪装成对象";
if (o instanceof String s){
System.out.println(s.toUppperCase());
}
在上面的示例中,新变量的范围s
直观地限于if
分支。准确地说,变量在保证模式匹配的范围内,这也使以下代码有效:
if (o instanceof String s && !s.isEmpty()){
System.out.println(s.toUpperCase());
}
反之亦然
if (!(o instanceof String s)){
throw new RuntimeException("excepting string");
}
// s 在此范围内!
System.out.println(s.toUpperCase());
记录
记录减少了作为简单数据载体的类的样板代码:
record Point(int x, int y) { }
这个单行产生一个自动定义的记录类
- x 和 y 的字段(私有和最终)
- 所有字段的规范构造函数
- 所有领域的
Getters
-
equals
, hashCode
, 和toString
(考虑所有字段)
// 规范构造函数
Point p = new Point(1, 2);
// getters - 没有'get'前缀
p.x();
p.y();
// equals, hashCode, toString
p.equals(new Point(1, 2)); // true
p.hashCode(); // 依赖于x和y的值
p.toString(); // Point[x=1,y=2]
记录类的一些最重要的限制是它们
- 是不可变的(因为它们的字段是私有的和最终的)
- 是隐式最终的
- 无法定义其他实例字段
- 总是扩展
Record
类
然而,也可以:
- 定义其他方法
- 实现接口
- 自定义规范构造函数和访问器
record Point(int x, int y) {
// 显示规范构造函数
Point {
// 自定义验证
if (x < 0 || y < 0)
throw new IllegalArgumentException("no negative points allowed");
// 自定义调整(通常违背直觉)
x += 1000;
y += 1000;
// 对字段的赋值在最后自动法僧
}
// 显示访问器
public int x() {
// 自定义的代码
return this.x;
}
}
此外,可以在方法中定义本地记录:
public void withLocalRecord() {
record Point(int x, int y) { };
Point p = new Point(1, 2);
}
密封类
密封类明确列出允许的直接子类。其他类不得从此类扩展:
public sealed class Parent
permits ChildA, ChildB, ChildC { ... }
同样,密封接口明确列出允许的直接子接口和实现类:
sealed interface Parent
permits ChildA, ChildB, ChildC { ... }
列表中的类或接口permits
必须位于同一个包中(如果父模块位于命名模块中,则位于同一个模块中)。
所述permits
,如果亚类(或接口)位于同一文件内,可以省略列表:
public sealed class Parent {
final class Child1 extends Parent {}
final class Child2 extends Parent {}
final class Child3 extends Parent {}
}
permits
列表中的每个子类或接口都必须使用以下修饰符之一:
- final(不允许进一步扩展;仅用于子类,因为接口不能是最终的)
- 密封(允许进一步、有限的扩展)
- 非密封(允许再次无限扩展)