Java仍然是目前比较流行的编程语言,它更短的发布节奏让开发者每六个月左右就可以试用新的语言或平台功能,IntelliJ IDEA帮助我们更流畅地发现和使用这些新功能。
IntelliJ IDEA v2022.3正式版下载(Q技术交流:786598704)
在本文中,我们将只介绍Java 19的语言功能:记录模式和switch模式匹配(第三版预览),特意避开其他Java 19功能,例如预览API虚拟线程。IntelliJ IDEA支持虚拟线程的基本语法高亮显示,官方团队正努力在调试器和分析器中添加对虚拟线程的支持。
记录模式简化了对记录组件的访问,比较记录模式和记录析构 – 当实例与记录结构匹配时,将记录组件的值提取到一组变量,它与 switch 和密封类的模式匹配等其他语言功能结合使用时,效果十分惊人。
switch 的模式匹配将模式添加到 switch 语句和 switch 表达式中的 case 标签,可以与 switch 一起使用的选择器表达式的类型扩展为任意引用值。 另外case 标签不再限于常量值,它还有助于将 if-else 语句链替换为 switch,提高代码可读性。 在这篇博文中,我们将介绍 switch 模式匹配第三版预览中引入的更改。
先从为使用 Java 19 功能对IntelliJ IDEA 进行配置开始。
IntelliJ IDEA 2022.3 中提供了对 Java 19 的支持,未来的 IntelliJ IDEA 版本将提供更多支持。 要通过 Java 19 使用 switch 的模式匹配,先转到 Project Settings | Project(项目设置 | 项目),将 Project SDK(项目 SDK)设为 19,然后将项目语言级别设置为 19 (Preview) – Record patterns, pattern matching for switch (third preview):
开发者可以使用系统上已经下载的任意版本JDK,也可以点击 Edit(编辑),然后选择 Add SDK >(添加 SDK)、Download JDK…(下载 JDK…)来下载其他版本,可以从供应商列表中选择要下载的 JDK 版本。
在 Modules(模块)选项卡上,确保为模块选择相同的语言级别 – 19 (Preview) – Record patterns, pattern matching for switch (third preview):
选择此选项后,可能会出现以下弹出窗口,通知您 IntelliJ IDEA 可能会在后续版本中停止对 Java 预览语言功能的支持。 因为预览功能(暂且)不是永久性的,并且它可能在未来的 Java 版本中发生变化(甚至被移除)。
数据是大多数应用程序的核心,通常开发者使用的应用程序可以查找数据,或者以帮助开发者做出决策的方式处理数据。当然如果应用程序无法存储、检索或处理其数据,这是不可行的。
在最近的一个 Java 版本(第 16 版)中,记录被添加到 Java 语言中,让开发者可以轻松处理数据。 记录大幅简化了对不可变数据建模的方式,它们充当数据的透明载体或包装器,只需使用一行代码,就可以定义一条记录及其组件。
例如,以下单行代码会创建一条新记录 Person,后者可以存储其组件 name 的字符串值和 age 的整数值:
record Person (String name, int age) { }
记录让开发者不必些示例代码,记录会隐式生成其构造函数的默认实现、其组件的访问器方法,以及 toString、equals 和 hashCode 等效用函数方法。 使用记录作为数据的包装器时,您很可能需要将其展开来访问其组件。 例如,对于记录 Person 的实例,可能想要检查其年龄组件以确定它所代表的人是否有资格投票,isEligibleToVote 这样的方法可以完成这个操作:
boolean isEligibleToVote(Object obj) {
if (obj instanceof Person person) {
return person.age() >= 18;
}
return false;
}
前面的示例使用了 instanceof 的模式匹配,它声明了一个模式变量 person,因此您不需要创建局部变量来将 obj 转换为 Person。
记录模式更进一步。 它不仅将实例与记录类型 Person 比较,还声明了记录组件的变量,因此无需定义局部变量或使用模式变量来访问记录的组件,这都要归功于编译器知道记录组件的确切数量和类型。
使用记录模式重写前面的方法,将记录类型与 instanceof 运算符配合使用或在 switch case 标签中使用时,IntelliJ IDEA可以检测到它并建议使用记录模式:
这是修改后的代码:
boolean isEligibleToVote(Object obj) {
if (obj instanceof Person(String name, int age)) {
return age >= 18;
}
return false;
}
在前面的代码中,记录模式 Person(String name, int age) 似乎允许使用变量 age 代替 person.age()。 然而继续阅读,将了解记录模式能够简化代码的意图,还有助于创建简洁的数据处理代码。
记录模式后面可以跟随一个记录模式变量,这种情况下,记录模式被称为命名记录模式(虽然没有得到确认,但 Java 20 的记录模式的第二版预览可能会放弃对命名记录模式的支持)。
记录模式还可以为其组件定义模式变量,用命名记录模式并尝试使用记录模式变量访问其组件时,IntelliJ IDEA 会提示您为其组件使用模式变量,将看到此类代码以黄色背景高亮显示,可以使用 Alt+Enter 查看建议,并接受修改代码的建议:
我们回顾一下上一部分中的 isEligibleToVote 方法示例,如果将 null 值传递给以下方法会发生什么:
boolean isEligibleToVote(Object obj) {
if (obj instanceof Person(String name, int age)) {
return age >= 18;
}
return false;
}
由于 null 不是记录模式 Person(String name, int age) 的实例,instanceof 运算符返回 false,并且模式变量 name 和 age 未初始化。这很方便,因为记录模式会处理null,开发者不需要定义非 null 检查。
但是如果组件 name 的值为 null,则模式将被匹配。
将另一条记录定义为其组件的记录相当常见,例如:
record Name (String fName, String lName) { }
record PhoneNumber(String areaCode, String number) { }
record Country (String countryCode, String countryName) { }
record Passenger (Name name,
PhoneNumber phoneNumber,
Country from,
Country destination) { }
如果没有可以检查 null 组件值的记录模式,您将需要几个 null 检查运算来处理记录 Passenger 的 fName 和 countryCode 的组件值,如下所示:
boolean checkFirstNameAndCountryCode (Object obj) {
if (obj != null) {
if (obj instanceof Passenger passenger) {
Name name = null;
Country destination = null;if (passenger.name() != null) {
name = passenger.name();if (passenger.destination() != null) {
destination = passenger.destination();String fName = name.fName();
String countryCode = destination.countryCode();if (fName != null && countryCode != null) {
return fName.startsWith("Simo") &&
countryCode.equals("PRG");
}
}
}
}
}
return false;
}
同样的行为可以通过嵌套记录模式实现,这也将使代码的意图更加清晰。 如果记录组件 name 和 destination 为 null,instanceof 检查将失败:
boolean checkFirstNameAndCountryCodeAgain (Object obj) {
if (obj instanceof Passenger(Name (String fName, String lName),
PhoneNumber phoneNumber,
Country from,
Country (String countryCode, String countryName) )) {if (fName != null && countryCode != null) {
return fName.startsWith("Simo") && countryCode.equals("PRG");
}
}
return false;
}
如前面的示例代码所示,开发者可以有选择地添加主记录组件的记录模式。 例如前面的示例没有为组件 from 使用记录模式,但它为主记录 Passenger 的记录组件目标使用记录模式。 简而言之,定义记录模式时,开发者可以控制要提取到模式变量的详细信息,这一功能非常适合数据处理密集型应用程序。
来回顾一下前面示例中的方法 checkFirstNameAndCountryCodeAgain,并将一些模式变量的类型定义为 var:
boolean checkFirstNameAndCountryCodeAgain (Object obj) {
if (obj instanceof Passenger(Name (String fName, var lName),
var phoneNumber,
Country from,
Country (var countryCode, String countryName) )) {if (fName != null && countryCode != null) {
return fName.startsWith("Simo") && countryCode.equals("PRG");
}
}
return false;
}
开发者可以将部分或全部模式变量的类型定义为var,如果您好奇它们的类型,IntelliJ IDEA 可以显示:
如果记录是泛型,则其记录模式必须使用泛型类型。 例如假设类 WristWatch 和泛型记录 Gift 的定义如下:
class WristWatch {}
record Gift(T t) {}
开发者可以使用以下方法解开记录 Gift 的实例,可以使用 var 或 WristWatch 作为模式变量 watch 的类型:
void unwrap(Gift obj) {
if (obj instanceof Gift (var watch)) {
System.out.println(watch);
}
}
但是,以下代码将不起作用:
static void cannotUnwap(Gift
下一部分使用记录模式和 switch 表达式创建强大的递归方法。
结合记录模式、switch 表达式和密封类,开发者可以创建功能强大、简洁且富有表现力的代码来处理数据。 这是密封接口 TwoDimensional 的示例,它由记录 Point、Line、Triangle 和 Square 实现:
sealed interface TwoDimensional {}
record Point (int x, int y) implements TwoDimensional { }
record Line ( Point start,
Point end) implements TwoDimensional { }
record Triangle( Point pointA,
Point pointB,
Point PointC) implements TwoDimensional { }
record Square ( Point pointA,
Point pointB,
Point PointC,
Point pointD) implements TwoDimensional { }
下面的方法定义了一个递归方法进程,它使用 switch 构造返回二维图形(如 Line、Triangle 或 Square)中所有点的 x 和 y 坐标之和:
static int process(TwoDimensional twoDim) {
return switch (twoDim) {
case Point(int x, int y) -> x + y;
case Line(Point a, Point b) -> process(a) + process(b);
case Triangle(Point a, Point b, Point c) ->
process(a) + process(b) + process(c);
case Square(Point a, Point b, Point c, Point d) ->
process(a) + process(b) + process(c) + process(d);
};
}
IntelliJ IDEA 还会在此方法的间距中显示递归调用图标: