写点什么

Java 高手速成 | 新增类 Record 的工作实例

作者:TiAmo
  • 2023-01-11
    江苏
  • 本文字数:7240 字

    阅读完需:约 24 分钟

Java高手速成 | 新增类Record的工作实例

01、什么是 Record?

Record 是 Java 新增的库类,在 Java 14 和 Java 15 中以预览(preview)形式公布。Record 类用来自动生成对定义数据进行创建、设置、访问以及比较等代码,所以又被称作数据类(data class)。在一些编程语言中,例如 Kotlin,已经使用数据类来处理数据模式建立(Object Relational Mapping-ORM)以及传输(Data Transfer Objects-DTOs)等处理。Record 类似于 Java 的枚举类(Enum),用来简化、定义和处理数据。传统的枚举类的编程方式和自动生成代码的 Record 类,这两者使得 Java 编程在保持简单性和灵活性中相互平衡和补充。

02、为什么支持 Record?

在应用软件开发中,编程人员经常会针对底层数据,进行对数据的构造器、访问方法(getters)、覆盖方法 equals、覆盖方法 hashCode、以及覆盖方法 toString 进行基础和重复性的编程。而使用 Record 类,程序中则可省去这些代码,而由支持 Record 的编译器自动生成。这不但提高了编程效率,而且提高了代码的可靠性。Record 类也可以提高数据在 ORM 和 DTOs 的一致性(persistency)要求。例如,使用 private 对数据的定义更加安全和规范化;自动生成的 equals 方法更加标准化和可靠;对生成的数据类定义为 final,因而限制对创建后的数据类的继承以及对已定义数据的修改等等。这些特性无疑提高了代码的标准化,使得对数据类的编程更加高效和可靠。

03、Record 编程举例

例 1. 利用 Record 创建一个名为 Circle 的数据类,产生对其半径 radius 进行基本处理功能。如同 Java 的任何类一样,Record 从 Object 类继承而来:

java.lang.Object
java.lang.Record
复制代码

Record 类提供一个特殊的构造器:

protected Record()
复制代码

利用 Record 创建数据类时,其参数可以是任何类型、以及任何数目的数据。先讨论一个简单例子,如:

record Circle(double radius) {} ; //创建并自动生成Circle构造器以及相关方法代码
复制代码

对以上代码编译后,将自动生成如下代码并将生成的 Circle 类定义为 final,即我们不能用它来继承子类,也不可以修改数据 radius 的值:

private final double radius = 0.0;    //定义数据     public Circle(double radius) {      //创建构造器        this.radius = radius;}public double radius() {            //创建访问方法    return radius;} @Overridepublic boolean equals(Object obj) {    //覆盖equals方法        if (this == obj) return true;    if (obj == null || getClass() != obj.getClass())      return false;    circle = (Circle) obj;if (radius != circle.radius())      return false;    else         return true; } @Overridepublic int hashCode() {            //覆盖哈西方法    return Objects.hash(radius);    //返回哈西代码} @Overridepublic String toString() {        //覆盖toString方法    return “Circle [radius= “ + radius + “]”;}
复制代码

 以上代码都是编译器自动生成的。在应用中我们可以直接调用这些方法,如:

package Example; public class CircleApp {     public static void main(String[] args) {         @SuppressWarnings("preview")         record Circle(double radius) {};         Circle circle = new Circle(2.345);         System.out.println(circle.radius());         Circle circle2 = new Circle(3.33);      //创建对象          System.out.println(circle.equals(circle2));     //false        System.out.println(circle.hashCode());      //显示哈西代码         System.out.println(circle);         //Circle [radius=2.345]    }}


复制代码

 以上利用 Record 创建 Circle 类的具体代码和运行结果见图 1 所示。


▍图 1. 在 Eclipse 中运行 Record 创建的 Circle 类以及运行结果

例 2. 利用 Record 创建一个名为 Person 的类。Person 包括两个数据类,具有数据 name 以及 ID 的 Student 类和具有 name 和 credit 的 Teacher 类。并调用自动生成的方法进行测试。Person 类的代码如下:

@SuppressWarnings("preview")record Student(String name, String ID) {}; //定义数据类Student @SuppressWarnings("preview")record Teacher(String name, int credit) {};//定义数据类Teacher
复制代码

对以上定义数据类 Student 和 Teacher 的代码编译后,将自动生成如下构造器和方法的代码:

Student 类:

@SuppressWarnings("preview")record Student(String name, String ID) {}; //定义数据类Student @SuppressWarnings("preview")record Teacher(String name, int credit) {};//定义数据类Teacher


复制代码

对以上定义数据类 Student 和 Teacher 的代码编译后,将自动生成如下构造器和方法的代码:

Student 类:

private final String name = null;                定义数据private final String ID = null; public Student (String name, String ID);            //创建构造器public String name();                    //创建方法public String ID();public blooean equals(Object obj);                //覆盖equals方法public int hashCode()                    //覆盖哈西方法public String toString();                    //覆盖toString方法
复制代码

Teacher 类:

private final String name = null;                定义数据private final int credit = 0; public Teacher (String name, int credit);            //构造器public String name();                    //访问方法public int credit();public blooean equals(Object obj);                //覆盖equals方法public int hashCode()                    //覆盖哈西方法public String toString();                    //覆盖toString方法
复制代码

值得指出的是,由于 Student 和 Teacher 各自都有有两个数据,在 Student 中覆盖后的 equals 方法代码如下:

@Override
public boolean equals(Object obj) { //覆盖equals方法
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass())
return false;
person = (Person) obj;
return (name.compareTo(person.name()) && ID.compareTo(person.ID()
&& ID.compareTo(person.ID());
}
复制代码

在 Teacher 中覆盖后的 equals 方法代码如下:

@Overridepublic boolean equals(Object obj) {                //覆盖equals方法        if (this == obj) return true;    if (obj == null || getClass() != obj.getClass())        return false;    person = (Person) obj;return (name.compareTo(person.name()) && (credit== person.credit()); }
复制代码

以上利用 Record 创建数据类 Student 和 Teacher 以及其测试程序如图 2 所示。


▍图 2. 在 Eclipse 中运行 record 创建的数据类

这个程序的运行结果如下:

Wang Lin112233false-2104304052Student[name=Li Gong, ID=445566]Name = Dr. Zhang, credit = 10, Count = 0Name = Mr. Qian, credit = 8, Count = 012-793483106-1753498516falseTeacher[name=Mr. Qian, credit=8]


复制代码

04、可以在 Record 创建数据类时加入其他代码吗?

我们只可以增添静态数据和静态方法以及对数据的访问方法。以 Circle2 为例:

@SuppressWarnings("preview")record Circle2(double radius) {        static int count;               //加入静态数据         public static void doCount() {      //加入静态方法            count++;        }           public static int getCount() {      //静态数据访问方法            return count;        }        public String getDiameter() {       //加入对数的访问方法            return "Diameter = " + radius *2;        }}
复制代码

以上扩充或修改不会影响 Record 对 Circle2 的创建和自动生成的各个方法。其测试程序和运行结果截图如图 3 所示:


▍图 3. 在 Eclipse 中运行 record 创建的数据类 Circle2

05、解析 Java 编译器对创建数据类产生的代码(提高篇)

在操作系统中利用 Java 编译指令 javap 可以观察编译器在创建数据类时产生的详细代码描述和规范清单。这个代码清单用可能超出我们对 Java 应用程序的编程范围,这里只是给读者提供一个概况性的解释。

我们以例 1 中讨论过的 Circle 为例,在操作系统中进入储存 Circl 的目录,打入如下编译指令:

javac --enable-preview --release 15 Circle.java
复制代码

编译器将显示如下提示信息:

Note: Circle.java uses preview language features.Note: Recompile with -Xlint:preview for details.


复制代码

表示正在使用 Java 的预览特性编译代码。再打入如下执行指令:

javap -v -p Circle.class
复制代码

如图 4 所示。


▍图 4.  在操作系统中打入指定的 javac 指令编译预览库类

产生 java 编译器生成的文件 Circle.class 后,打入:

javap -v -p Circle.class
复制代码

这个指令将显示 JVM 生成的对数据类 Circle 的详细代码规范清单,如下所示:

C:\Users\ygao\eclipse-workspace\myProject\src\example>javap -v -p Circle.classClassfile /C:/Users/ygao/eclipse-workspace/myProject/src/example/Circle.class  Last modified Jan 5, 2021; size 1096 bytes  MD5 checksum a7717f26f0812dac23eb89a37ccbc91d  Compiled from "Circle.java"final class example.Circle extends java.lang.Record  minor version: 65535  major version: 59  flags: (0x0030) ACC_FINAL, ACC_SUPER  this_class: #8                          // example/Circle  super_class: #2                         // java/lang/Record  interfaces: 0, fields: 1, methods: 5, attributes: 4Constant pool:   #1 = Methodref          #2.#3          // java/lang/Record."<init>":()V   #2 = Class              #4             // java/lang/Record   #3 = NameAndType        #5:#6          // "<init>":()V   #4 = Utf8               java/lang/Record   #5 = Utf8               <init>   #6 = Utf8               ()V   #7 = Fieldref           #8.#9          // example/Circle.radius:D   #8 = Class              #10            // example/Circle   #9 = NameAndType        #11:#12        // radius:D  #10 = Utf8               example/Circle  #11 = Utf8               radius  #12 = Utf8               D  #13 = InvokeDynamic      #0:#14         // #0:toString:(Lexample/Circle;)Ljava/lang/String;  #14 = NameAndType        #15:#16        // toString:(Lexample/Circle;)Ljava/lang/String;  #15 = Utf8               toString  #16 = Utf8               (Lexample/Circle;)Ljava/lang/String;  #17 = InvokeDynamic      #0:#18         // #0:hashCode:(Lexample/Circle;)I  #18 = NameAndType        #19:#20        // hashCode:(Lexample/Circle;)I  #19 = Utf8               hashCode  #20 = Utf8               (Lexample/Circle;)I  #21 = InvokeDynamic      #0:#22         // #0:equals:(Lexample/Circle;Ljava/lang/Object;)Z  #22 = NameAndType        #23:#24        // equals:(Lexample/Circle;Ljava/lang/Object;)Z  #23 = Utf8               equals  #24 = Utf8               (Lexample/Circle;Ljava/lang/Object;)Z  #25 = Utf8               (D)V  #26 = Utf8               Code  #27 = Utf8               LineNumberTable  #28 = Utf8               MethodParameters  #29 = Utf8               ()Ljava/lang/String;  #30 = Utf8               ()I  #31 = Utf8               (Ljava/lang/Object;)Z  #32 = Utf8               ()D  #33 = Utf8               SourceFile  #34 = Utf8               Circle.java  #35 = Utf8               Record  #36 = Utf8               BootstrapMethods  #37 = MethodHandle       6:#38          // REF_invokeStatic java/lang/runtime/ObjectMethods.bootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;  #38 = Methodref          #39.#40        // java/lang/runtime/ObjectMethods.bootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;  #39 = Class              #41            // java/lang/runtime/ObjectMethods  #40 = NameAndType        #42:#43        // bootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;  #41 = Utf8               java/lang/runtime/ObjectMethods  #42 = Utf8               bootstrap  #43 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;  #44 = String             #11            // radius  #45 = MethodHandle       1:#7           // REF_getField example/Circle.radius:D  #46 = Utf8               InnerClasses  #47 = Class              #48            // java/lang/invoke/MethodHandles$Lookup  #48 = Utf8               java/lang/invoke/MethodHandles$Lookup  #49 = Class              #50            // java/lang/invoke/MethodHandles  #50 = Utf8               java/lang/invoke/MethodHandles  #51 = Utf8               Lookup{  private final double radius;    descriptor: D    flags: (0x0012) ACC_PRIVATE, ACC_FINAL   example.Circle(double);    descriptor: (D)V    flags: (0x0000)    Code:      stack=3, locals=3, args_size=2         0: aload_0         1: invokespecial #1                  // Method java/lang/Record."<init>":()V         4: aload_0         5: dload_1         6: putfield      #7                  // Field radius:D         9: return      LineNumberTable:        line 4: 0    MethodParameters:      Name                           Flags      radius   public final java.lang.String toString();    descriptor: ()Ljava/lang/String;    flags: (0x0011) ACC_PUBLIC, ACC_FINAL    Code:      stack=1, locals=1, args_size=1         0: aload_0         1: invokedynamic #13,  0             // InvokeDynamic #0:toString:(Lexample/Circle;)Ljava/lang/String;         6: areturn      LineNumberTable:        line 3: 0   public final int hashCode();    descriptor: ()I    flags: (0x0011) ACC_PUBLIC, ACC_FINAL    Code:      stack=1, locals=1, args_size=1         0: aload_0         1: invokedynamic #17,  0             // InvokeDynamic #0:hashCode:(Lexample/Circle;)I         6: ireturn      LineNumberTable:        line 3: 0   public final boolean equals(java.lang.Object);    descriptor: (Ljava/lang/Object;)Z    flags: (0x0011) ACC_PUBLIC, ACC_FINAL    Code:      stack=2, locals=2, args_size=2         0: aload_0         1: aload_1         2: invokedynamic #21,  0             // InvokeDynamic #0:equals:(Lexample/Circle;Ljava/lang/Object;)Z         7: ireturn      LineNumberTable:        line 3: 0   public double radius();    descriptor: ()D    flags: (0x0001) ACC_PUBLIC    Code:      stack=2, locals=1, args_size=1         0: aload_0         1: getfield      #7                  // Field radius:D         4: dreturn      LineNumberTable:        line 3: 0}SourceFile: "Circle.java"Error: unknown attribute  Record: length = 0x8   00 01 00 0B 00 0C 00 00BootstrapMethods:  0: #37 REF_invokeStatic java/lang/runtime/ObjectMethods.bootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;    Method arguments:      #8 example/Circle      #44 radius      #45 REF_getField example/Circle.radius:DInnerClasses:  public static final #51= #47 of #49;    // Lookup=class java/lang/invoke/MethodHandles


复制代码

总结一下这个代码清单告诉我们的有关 Circle 类规范化信息:

1. 正如我们讨论过的,Circle 被定义为 final,所以我们不能修改它的数据,也不能改变自动生成的方法以及不能继承子类。

2. 它从 java.lang.Record 继承而来。它具有一个数据,5 个方法以及 4 个参数(attributes)。

数据:private final double radius;初始化值为 0.0。

方法:public final String toString()、public final int hashCode()、public final boolean equals(Object)、 public final double radius()以及构造器 public Circle(double)。

3. 这些方法由方法处理器(Method Handles)在调用时激活和援引(Invoke Dynamic)。

4. 一个启动方法 ObjectMethod.bootstrap 利用数据类中的数据名如 radius 和访问方法,如 radius()来产生其他各个方法, 如 toString, hashCode(), equals()以及 Circle(double)。


发布于: 2023-01-11阅读数: 14
用户头像

TiAmo

关注

有能力爱自己,有余力爱别人! 2022-06-16 加入

CSDN全栈领域优质创作者;阿里云创作者社区专家博主、技术博主、星级博主;华为云享专家;

评论

发布
暂无评论
Java高手速成 | 新增类Record的工作实例_新特性_TiAmo_InfoQ写作社区