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和强制转换习用法变得更简洁、更不易出错:

// <Java 16if (obj instanceof String) {    String s = (String)obj;    ... 应用s ...}// ≥Java 16if (obj instanceof String s) {    ... 应用s ...}

新代码中,若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  RecordPatternTypePattern:  LocalVariableDeclarationRecordPattern:  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 公布!