Java 23中原始类型的匹配模式

每半年,我们都会获得一个全新、新鲜、美味的 Java 版本。现在我们得到了 Java 23,本文将讨论JEP-455,它引入了模式中的原始类型、instanceof 和 switch。请记住,这是一个预览功能。

Java 23 之前
让我们回顾一下这个 JEP 之前的世界。每当我们想要使用 switch 或 instanceof 时,我们都会受到很大限制。对于 switch,我们无法使用所有装箱类型。

无论何时你想写这段代码,

Double d = 20.0d;
switch(d){
   case 20.0d -> log("double is 20");
   default -> log(
"wrong number" + d);
}

您将收到编译错误:

java: constant label of type double is not compatible with switch selector type java.lang.Double  

这是因为某些类型存在限制。在JLS中,您可以看到:

“Expression 称为选择器表达式。选择器表达式的类型必须是 char、byte、short、int 或引用类型,否则会发生编译时错误。”

从这句话中,我们可以了解到int类型可以在 switch 中使用。没错,你可以创建如下代码:

int v = 20;
switch(v){
   case 20 -> log("int is 20");
   default -> log(
"wrong number" + v);
}

一切都会正常工作。正如您可能从我们的博客文章中知道或读到的那样,Java 21 中的 switch 进行了大量升级,您可以执行以下操作:

Integer v = 20;
switch(v){
   case Integer i when i < 20-> log("This value is too low: " + i );
   case Integer i -> log(
"This value is perfect: " + i );
}

看起来不错吧?我们可以对原始 int 尝试类似的方法。

int v = 20;
switch(v){
   case int i when i < 20 -> log("This value is too low: " + i );
   case int i -> log(
"This value is perfect: " + i );
}

我们运行此代码,并收到编译错误:

java: unexpected type

  required: class or array

  found:    int

这是为什么呢?在 JEP-455 之前,Java 对 switch 模式的支持有限。

类似这样的切换模式对于记录模式来说是可能的。

record Value(int i){}
void test(){
   Value v = new Value(20);
   switch (v) {
       case Value(int i) when i < 20 -> log("This value is too low: " + i );
       case Value(int i) -> log(
"This value is perfect: " + i );
   }
}

正常工作。Instanceof 不适用于任何原始类型。

Java 23 之后 - 启用预览功能时
现在允许使用 Long、Float、Double 和 Boolean 进行切换。因此,我们的 Double 预览示例可以正常工作。

Instanceof 也改变了它们的行为。可以检查变量是否属于主要类型。

从现在开始,当我们运行如下代码时:

int i = 42;
if(i instanceof int){
   log("i is an instance of int");
}

控制台上将显示“i is an instance of int”。太棒了!这段代码怎么样?

int i = 42;
if(i instanceof byte){
   log("i is an instance of byte?");
}

这很简单,对吧?我们什么也不会得到,因为int不是byte的实例。不幸的是,在这个例子中,我们将得到文本“i is an instance of byte?”

这是为什么?

一切都是因为转换。Java 的这个简单特性就是将一种原始数据类型的值赋给另一种类型。

你知道强制转换并不安全吗?让我们来看这个例子:

int i = 42;
byte b = (byte) i;
log("b value is " + b);

我们的 b 变量的值将是 42,对吗?现在怎么样?

int i = 128;
byte b = (byte) i;
log("b value is " + b);

你可能觉得这个问题有点可疑。你是对的。b 的值应该是-128。这是因为字节类型的范围是从 -128 到 127(含)。在这里,你可以了解有关整数类型和值的更多信息。

那么问题来了,如果值在转换后会发生变化,那么它是否是正确的类型?从 JEP 中我们得知答案是否定的。

这就是为什么 instanceof 现在不仅检查类型是否适合引用,而且还检查是否可以安全地转换原始值。

这会带来什么后果?

在 Java 23 之前,当您尝试如下代码时:

Integer i = 42;
switch (i) {
   case Double d -> log("i is " + d); 
}

你收到一个错误:

error: incompatible types: Integer cannot be converted to Double

但是对于原始类型,代码如下:

int i = 42;
switch (i) {
   case double d -> log("i is " + d)
}

它工作得很好,因为每个 int 都可以毫无问题地转换为 double。当你想用更窄的类型尝试同样的情况时,你需要确保你的 switch 是详尽的。就像这样:

int i = 10_0000;
switch (i) {
   case short v -> log("i is " + v);
   default -> log(
"not in scope of short" + i);
}

如果您想知道将原始类型转换为其他类型是否安全,您可以查看JLS 中的转换上下文

概括
Swtich模式在我们的日常工作中变得越来越有用。得益于 Project Amber,我们现在可以更轻松地使用原语,并且使用更少的冗长方法。