乐趣区

关于后端:JDK21新特性Record-Patterns记录模式详解

1 摘要

通过应用记录模式来加强 Java 编程语言,以解构记录值。记录模式和类型模式可嵌套应用,从而实现弱小、申明式和可组合的数据导航和解决模式。

2 发展史

由 JEP 405 提出的预览性能,并在 JDK 19 公布,而后由 JEP 432 再次预览,并在 JDK 20 公布。该性能与用于 switch 的模式匹配(JEP 441)独特演进,并且二者有相当大的交互作用。本 JEP 提议在继续的教训和反馈根底上对该功能完善。

除了一些主要的编辑更改,自第二个预览版以来的次要变动是删除了对加强 for 语句头部呈现记录模式的反对。这个性能可能会在将来的 JEP 中重提。

3 指标

  • 扩大模式匹配以解构记录类的实例,实现更简单的数据查问
  • 增加嵌套模式,实现更可组合的数据查问

4 动机

Java 16 中,JEP 394 扩大了 instanceof 运算符,使其可承受类型模式并执行模式匹配。这个简略的扩大使得相熟的 instanceof 和强制转换习用法变得更简洁、更不易出错:

// 

新代码中,若 obj 在运行时是 String 的实例,则 obj 与类型模式 String s 匹配。若模式匹配胜利,则 instanceof true,且模式变量 s 被初始化为 obj 强制转换为 String 的值,而后能够在蕴含的代码块中应用。

类型模式一次性打消了许多类型转换的呈现。然而,它们只是朝着更申明式、以数据为焦点的编程格调迈出的第一步。随 Java 反对新的、更具表现力的数据建模,模式匹配可通过让开发表白模型的语义用意来简化对这些数据的应用。

5 Pattern matching 和 records

记录 (JEP 395) 是数据的通明载体。接管记录类实例的代码通常会应用内置的组件拜访器办法提取数据,即组件。

5.1 Point 的实例

如用类型模式测试一个值是否是记录类 Point 的实例,并在匹配胜利时从该值中提取 x 和 y 组件。

Java8

class Point {
    private int x;
    private int y;
    
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
    
    public int getX() {return x;}
    
    public int getY() {return y;}
}

static void printSum(Object obj) {if (obj instanceof Point) {Point p = (Point) obj;
        int x = p.getX();
        int y = p.getY();
        System.out.println(x + y);
    }
}

≥Java 16

record Point(int x, int y) {}

static void printSum(Object obj) {if (obj instanceof Point p) {int x = p.x();
        int y = p.y();
        System.out.println(x+y);
    }
}

仅应用模式变量 p 调用拜访办法 x()、y(),这些办法返回组件 x 和 y 的值。

在每个记录类中,其拜访办法和组件之间存在一对一对应关系。

如果模式不仅可测试一个值是否是 Point 的实例,还可间接从该值中提取 x 和 y 组件,从而代表咱们调用拜访器办法的用意将更好。换句话说:

// Java 21 及当前
static void printSum(Object obj) {if (obj instanceof Point(int x, int y)) {System.out.println(x+y);
    }
}

Point(int x, int y) 是一个 record pattern。它将用于提取组件的局部变量的申明间接晋升到模式自身,并在值与模式匹配时通过调用拜访办法对这些变量初始化。实际上,record pattern 将记录的实例解构为其组件。

6 嵌套 record pattern

模式匹配的真正威力在于优雅扩大到匹配更简单的对象图。

思考以下申明:

// Java 16 及当前
record Point(int x, int y) {}
enum Color {RED, GREEN, BLUE}
record ColoredPoint(Point p, Color c) {}
record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {}

已知可应用记录模式提取对象的组件。如想从左上角点提取色彩:

// Java 21 及当前
static void printUpperLeftColoredPoint(Rectangle r) {if (r instanceof Rectangle(ColoredPoint ul, ColoredPoint lr)) {System.out.println(ul.c());
    }
}

但 ColoredPoint 值 ul 自身是个记录值,心愿进一步合成。因而,记录模式反对嵌套,容许对记录组件进一步匹配、合成。可在记录模式中嵌套另一个模式,同时对外部和外部记录合成:

// Java 21 及当前
static void printColorOfUpperLeftPoint(Rectangle r) {if (r instanceof Rectangle(ColoredPoint(Point p, Color c),
                               ColoredPoint lr)) {System.out.println(c);
    }
}

嵌套模式容许以与组装对象的代码一样清晰简洁形式拆解聚合。如创立一个矩形,通常会将构造函数嵌套在一个表达式中:

// Java 16 及当前
Rectangle r = new Rectangle(new ColoredPoint(new Point(x1, y1), c1), 
                            new ColoredPoint(new Point(x2, y2), c2));

应用嵌套模式,咱们能够应用与嵌套构造函数构造类似的代码来解构这样的矩形:

// Java 21 及当前
static void printXCoordOfUpperLeftPointWithPatterns(Rectangle r) {if (r instanceof Rectangle(ColoredPoint(Point(var x, var y), var c),
                               var lr)) {System.out.println("Upper-left corner:" + x);
    }
}

嵌套模式可能无奈匹配:

// Java 21 及当前
record Pair(Object x, Object y) {}
Pair p = new Pair(42, 42);
if (p instanceof Pair(String s, String t)) {System.out.println(s + "," + t);
} else {System.out.println("Not a pair of strings");
}

这里的记录模式 Pair(String s, String t) 蕴含了两个嵌套的类型模式,即 String s 和 String t。如果一个值与模式 Pair(String s, String t) 匹配,那么它是一个 Pair,并且递归地,它的组件值与类型模式 String s 和 String t 匹配。在咱们下面的示例代码中,因为记录的两个组件值都不是字符串,因而这些递归的模式匹配失败,因而执行 else 块。

总之,嵌套模式打消了导航对象的意外复杂性,使咱们能专一这些对象所示意的数据。它们还赋予咱们集中处理错误的能力,因为如果一个值无奈与嵌套模式 P(Q) 匹配,那子模式 P 和 Q 中的任何一个或两个都无奈匹配。咱们不须要检查和解决每个独自的子模式匹配失败——要么整个模式匹配,要么不匹配。

7 形容

应用可嵌套的记录模式。

模式语法变为:

Pattern:
  TypePattern
  RecordPattern

TypePattern:
  LocalVariableDeclaration

RecordPattern:
  ReferenceType ([ PatternList] )

PatternList: 
  Pattern {, Pattern}

8 记录模式

由记录类类型和(可能为空的)模式列表组成,该列表用于与相应的记录组件值进行匹配。

如申明

record Point(int i, int j) {}

如果值 v 与记录模式 Point(int i, int j) 匹配,则它是记录类型 Point 的实例;如这样,模式变量 i 将被初始化为在值 v 上调用与 i 对应的拜访器办法的后果,模式变量 j 将被初始化为在值 v 上调用与 j 对应的拜访器办法的后果。(模式变量的名称不须要与记录组件的名称雷同;也就是说,记录模式 Point(int x, int y) 的行为雷同,只是模式变量 x 和 y 被初始化。)

null 值不与任何记录模式匹配。

记录模式可用 var 来匹配记录组件,而无需申明组件的类型。在这种状况下,编译器会推断由 var 模式引入的模式变量的类型。如模式 Point(var a, var b) 是模式 Point(int a, int b) 的简写。

记录模式申明的模式变量汇合包含模式列表中申明的所有模式变量。

如果一个表达式能够在不须要未经查看的转换的状况下将其转换为模式中的记录类型,则该表达式与记录模式兼容。

如果记录模式命名了一个泛型记录类,但没有给出类型参数(即,记录模式应用原始类型),则始终会推断类型参数。例如:

// Java 21 及当前
record MyPair<S,T>(S fst, T snd){};
static void recordInference(MyPair<String, Integer> pair){switch (pair) {case MyPair(var f, var s) -> 
            ... // 推断的记录模式 MyPair<String,Integer>(var f, var s)
        ...
    }
}

记录模式的类型参数推断在反对记录模式的所有构造中都受到反对,即 instanceof 表达式和 switch 语句和表达式。

推断实用于嵌套记录模式;例如:

// Java 21 及当前
record Box<T>(T t) {}
static void test1(Box<Box<String>> bbs) {if (bbs instanceof Box<Box<String>>(Box(var s))) {System.out.println("String" + s);
    }
}

这里,嵌套模式 Box(var s) 的类型参数被推断为 String,因而模式自身被推断为 Box<String>(var s)。

甚至可省略内部记录模式中的类型参数,失去简洁代码:

// Java 21 及当前
static void test2(Box<Box<String>> bbs) {if (bbs instanceof Box(Box(var s))) {System.out.println("String" + s);
    }
}

这里编译器会推断整个 instanceof 模式为 Box<Box<String>>(Box<String>(var s))

为放弃兼容性,类型模式不反对隐式推断类型参数;如类型模式 List l 始终被视为原始类型模式。

9 记录模式和残缺的 switch

JEP 441 加强了 switch 表达式和 switch 语句,以反对模式标签。无论是 switch 表达式还是模式 switch 语句,都必须是残缺的:switch 块必须有解决选择器表达式的所有可能值的子句。对于模式标签,这是通过剖析模式的类型来确定的;例如,case 标签 case Bar b 匹配类型为 Bar 及其所有可能的子类型的值。

对于波及记录模式的模式标签,剖析更加简单,因为咱们必须思考组件模式的类型,并对密封层次结构进行调整。例如,思考以下申明:

class A {}
class B extends A {}
sealed interface I permits C, D {}
final class C implements I {}
final class D implements I {}
record Pair<T>(T x, T y) {}
Pair<A> p1;
Pair<I> p2;

以下 switch 不是残缺的,因为没有匹配蕴含两个类型为 A 的值的对:

// Java 21 及当前
switch (p1) {                 // 谬误!case Pair<A>(A a, B b) -> ...
    case Pair<A>(B b, A a) -> ...
}

这两个 switch 是残缺的,因为接口 I 是密封的,因而类型 C 和 D 涵盖了所有可能的实例:

// Java 21 及当前
switch (p2) {case Pair<I>(I i, C c) -> ...
    case Pair<I>(I i, D d) -> ...
}

switch (p2) {case Pair<I>(C c, I i) -> ...
    case Pair<I>(D d, C c) -> ...
    case Pair<I>(D d1, D d2) -> ...
}

相比之下,这个 switch 不是残缺的,因为没有匹配蕴含两个类型为 D 的值的对:

// Java 21 及当前
switch (p2) {                        // 谬误!case Pair<I>(C fst, D snd) -> ...
    case Pair<I>(D fst, C snd) -> ...
    case Pair<I>(I fst, C snd) -> ...
}

10 将来

记录模式的形容中提到了许多能够扩大这里形容的记录模式的方向:

  • 可变参数模式,用于可变数量的记录
  • 匿名模式,能够呈现在记录模式的模式列表中,匹配任何值,但不申明模式变量
  • 实用于任意类的值而不仅仅是记录类的模式。

咱们能够在将来的 JEP 中思考其中的一些方向。

11 依赖关系

本 JEP 建设在 Pattern Matching for instanceof(JEP 394)的根底上,该性能已在 JDK 16 中公布。它与 Pattern Matching for switch(JEP 441)独特演进。

本文由博客一文多发平台 OpenWrite 公布!

退出移动版