JEP 455:模式、instanceof 和 switch 中的原始类型(预览版)
概括
通过允许所有模式上下文中的基本类型模式来增强模式匹配,并扩展instanceof
和switch
使用所有基本类型。这是预览语言功能。
目标
-
通过允许所有类型(无论是原始类型还是引用类型)的类型模式来实现统一的数据探索。
-
将文字图案与 对齐
instanceof
,并instanceof
与安全铸造对齐。 -
允许模式匹配在嵌套和顶级上下文中使用原始类型模式。
-
提供易于使用的构造,消除由于不安全的强制转换而丢失信息的风险。
-
switch
遵循Java 5 (enumswitch
) 和 Java 7 (stringswitch
)中的增强功能,允许switch
处理任何基本类型的值。
非目标
- 向 Java 语言添加新类型的转换并不是目标。
动机
instanceof
在使用模式匹配、和时,与基元类型相关的多个限制会带来摩擦switch
。消除这些限制将使 Java 语言更加统一、更具表现力。
的模式匹配switch
不支持原始类型模式
第一个限制是switch
(JEP 441)的模式匹配不支持原始类型模式,即指定原始类型的类型模式。仅支持指定引用类型的类型模式,例如case Integer i
或case String s
。 (从 Java 21 开始,还支持记录模式 ( JEP 440switch
) 。)
通过对 中原始类型模式的支持switch
,我们可以改进switch
表达式
switch (x.getStatus()) {
case 0 -> "okay";
case 1 -> "warning";
case 2 -> "error";
default -> "unknown status: " + x.getStatus();
}
通过将default
子句转换为case
具有公开匹配值的原始类型模式的子句:
switch (x.getStatus()) {
case 0 -> "okay";
case 1 -> "warning";
case 2 -> "error";
case int i -> "unknown status: " + i;
}
支持原始类型模式还允许警卫检查匹配的值:
switch (x.getYearlyFlights()) {
case 0 -> ...;
case 1 -> ...;
case 2 -> issueDiscount();
case int i when i >= 100 -> issueGoldCard();
case int i -> ... appropriate action when i > 2 && i < 100 ...
}
记录模式对原始类型的支持有限
另一个限制是记录模式对原始类型的支持有限。记录模式通过将记录分解为其各个组件来简化数据处理。当组件是原始值时,记录模式必须精确地了解该值的类型。这对于开发人员来说很不方便,并且与 Java 语言的其余部分中存在的有用的自动转换不一致。
例如,假设我们希望处理通过这些记录类表示的 JSON 数据:
sealed interface JsonValue {
record JsonString(String s) implements JsonValue { }
record JsonNumber(double d) implements JsonValue { }
record JsonObject(Map<String, JsonValue> map) implements JsonValue { }
}
JSON 不区分整数和非整数,因此JsonNumber
用组件表示数字double
以实现最大的灵活性。但是,我们double
在创建记录时不需要传递a JsonNumber
;我们可以传递一个int
诸如30
,Java 编译器会自动将 扩展int
为double
:
var json = new JsonObject(Map.of("name", new JsonString("John"),
"age", new JsonNumber(30)));
JsonNumber
不幸的是,如果我们希望用记录模式来分解 a,Java 编译器并不那么乐于助人。由于JsonNumber
是用double
组件声明的,因此我们必须将 a 分解JsonNumber
为double
,并手动转换为int
:
if (json instanceof JsonObject(var map)
&& map.get("name") instanceof JsonString(String n)
&& map.get("age") instanceof JsonNumber(double a)) {
int age = (int)a; // unavoidable (and potentially lossy!) cast
}
换句话说,原始类型模式可以嵌套在记录模式内,但是不变的:模式中的原始类型必须与记录组件的原始类型相同。不可能分解过孔JsonNumber
并使instanceof JsonNumber(int age)
编译器自动将double
组件缩小到int
。
此限制的原因是缩小可能是有损的:double
对于变量来说,运行时分量的值可能太大,或者精度太高int
。然而,模式匹配的一个主要好处是它通过简单地不匹配来自动拒绝非法值。如果double
a 的组件JsonNumber
太大或太精确而无法安全地缩小到 an int
,则instanceof JsonNumber(int age)
可以简单地返回,让程序在不同的分支中false
处理大组件。double
这就是模式匹配已经适用于引用类型模式的方式。例如:
record Box(Object o) {}
var b = new Box(...);
if (b instanceof Box(RedBall rb)) ...
else if (b instanceof Box(BlueBall bb)) ...
else ....
此处 的组件Box
被声明为 类型Object
,但instanceof
可用于尝试将 aBox
与RedBall
组件或BlueBall
组件进行匹配。仅当运行时为 a并且其组件可以缩小为 时,记录模式Box(RedBall rb)
才匹配;类似地,仅当其分量可以缩小到时才匹配。b``Box``o``RedBall``Box(BlueBall bb)``o``BlueBall
在记录模式中,原始类型模式应该像引用类型模式一样顺利工作,JsonNumber(int age)
即使相应的记录组件是除int
.这将消除在匹配模式后进行冗长且可能有损的转换的需要。
模式匹配instanceof
不支持基本类型
另一个限制是instanceof
(JEP 394)的模式匹配不支持原始类型模式。仅支持指定引用类型的类型模式。 (从 Java 21 开始,还支持记录模式instanceof
。)
instanceof
原始类型模式在 中和在 中一样有用switch
。广义上来说,其目的instanceof
是测试一个值是否可以安全地转换为给定的类型;这就是为什么我们总是instanceof
近距离观察和投射操作。此测试对于基元类型至关重要,因为将基元值从一种类型转换为另一种类型时可能会发生信息丢失。
例如,将int
值转换为 afloat
是由赋值语句自动执行的,即使它可能有损 — 并且开发人员不会收到任何警告:
int getPopulation() {...}
float pop = getPopulation(); // silent potential loss of information
同时,将int
值转换为 abyte
是通过显式强制转换执行的,但强制转换可能是有损的,因此必须在其之前进行费力的范围检查:
if (i >= -128 && i <= 127) {
byte b = (byte)i;
... b ...
}
原始类型模式instanceof
将包含 Java 语言中内置的有损转换,并避免开发人员近三十年来手工编码的艰苦范围检查。换句话说,instanceof
可以检查值和类型。上面的两个例子可以重写如下:
if (getPopulation() instanceof float pop) {
... pop ...
}
if (i instanceof byte b) {
... b ...
}
该instanceof
运算符结合了赋值语句的便利性和模式匹配的安全性。如果输入 (getPopulation()
或i
) 可以安全地转换为原始类型模式中的类型,则模式匹配并且转换结果立即可用 (pop
或b
)。但是,如果转换会丢失信息,则模式不匹配,程序应该在不同的分支中处理无效输入。
instanceof
和中的原始类型switch
如果我们要解除对原始类型模式的限制,那么解除相关限制将会很有帮助:当instanceof
采用类型而不是模式时,它只采用引用类型,而不是原始类型。当采用原始类型时,instanceof
会检查转换是否安全,但不会实际执行它:
if (i instanceof byte) { // value of i fits in a byte
... (byte)i ... // traditional cast required
}
这一增强功能恢复了和instanceof
的语义之间的一致性,如果我们在一个上下文中允许原始类型但在另一个上下文中不允许原始类型,那么这种一致性就会丢失。instanceof T``instanceof T t
switch
最后,解除可以采用byte
、short
、char
和int
值但不能采用boolean
、float
、double
或long
值的限制会很有帮助。
切换boolean
值将是三元条件运算符 ( ?:
) 的有用替代方案,因为boolean
switch 可以包含语句和表达式。例如,以下代码使用boolean
开关在以下情况下执行一些日志记录false
:
startProcessing(OrderStatus.NEW, switch (user.isLoggedIn()) {
case true -> user.id();
case false -> { log("Unrecognized user"); yield -1; }
});