快速上手 Record 类
我们先举一个简单例子,声明一个用户 Record。
public record User(long id, String name, int age) {}
这样编写代码之后,Record 类默认包含的元素和方法实现包括:
- record 头指定的组成元素(
int id, String name, int age
),并且,这些元素都是 final 的。 - record 默认只有一个构造器,是包含所有元素的构造器。
- record 的每个元素都有一个对应的 getter(但这种 getter 并不是 getxxx(),而是直接用变量名命名,所以使用序列化框架,DAO 框架都要注意这一点)
- 实现好的 hashCode(),equals(),toString() 方法(通过自动在编译阶段生成关于 hashCode(),equals(),toString() 方法实现的字节码实现)。
我们来使用下这个 Record :
User zhx = new User(1, "zhx", 29); User ttj = new User(2, "ttj", 25); System.out.println(zhx.id());//1 System.out.println(zhx.name());//zhx System.out.println(zhx.age());//29 System.out.println(zhx.equals(ttj));//false System.out.println(zhx.toString());//User[id=1, name=zhx, age=29] System.out.println(zhx.hashCode());//3739156
Record 的结构是如何实现的
编译后插入相关域与方法的字节码
查看上面举得例子的字节码,有两种方式,一是通过 javap -v User.class
命令查看文字版的字节码,截取重要的字节码如下所示:
//省略文件头,文件常量池部分 { //public 构造器,全部属性作为参数,并给每个 Field 赋值 public com.github.hashzhang.basetest.User(long, java.lang.String, int); descriptor: (JLjava/lang/String;I)V flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=5, args_size=4 0: aload_0 1: invokespecial #1 // Method java/lang/Record."<init>":()V 4: aload_0 5: lload_1 6: putfield #7 // Field id:J 9: aload_0 10: aload_3 11: putfield #13 // Field name:Ljava/lang/String; 14: aload_0 15: iload 4 17: putfield #17 // Field age:I 20: return LineNumberTable: line 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 21 0 this Lcom/github/hashzhang/basetest/User; 0 21 1 id J 0 21 3 name Ljava/lang/String; 0 21 4 age I MethodParameters: Name Flags id name age //public final 修饰的 toString 方法 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 //核心实现是这个 invokedynamic,我们后面会分析 1: invokedynamic #21, 0 // InvokeDynamic #0:toString:(Lcom/github/hashzhang/basetest/User;)Ljava/lang/String; 6: areturn LineNumberTable: line 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 7 0 this Lcom/github/hashzhang/basetest/User; //public final 修饰的 hashCode 方法 public final int hashCode(); descriptor: ()I flags: (0x0011) ACC_PUBLIC, ACC_FINAL Code: stack=1, locals=1, args_size=1 0: aload_0 //核心实现是这个 invokedynamic,我们后面会分析 1: invokedynamic #25, 0 // InvokeDynamic #0:hashCode:(Lcom/github/hashzhang/basetest/User;)I 6: ireturn LineNumberTable: line 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 7 0 this Lcom/github/hashzhang/basetest/User; //public final 修饰的 equals 方法 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 //核心实现是这个 invokedynamic,我们后面会分析 2: invokedynamic #29, 0 // InvokeDynamic #0:equals:(Lcom/github/hashzhang/basetest/User;Ljava/lang/Object;)Z 7: ireturn LineNumberTable: line 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 8 0 this Lcom/github/hashzhang/basetest/User; 0 8 1 o Ljava/lang/Object; //public 修饰的 id 的 getter public long id(); descriptor: ()J flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: aload_0 1: getfield #7 // Field id:J 4: lreturn LineNumberTable: line 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/github/hashzhang/basetest/User; //public 修饰的 name 的 getter public java.lang.String name(); descriptor: ()Ljava/lang/String; flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: getfield #13 // Field name:Ljava/lang/String; 4: areturn LineNumberTable: line 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/github/hashzhang/basetest/User; //public 修饰的 age 的 getter public int age(); descriptor: ()I flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: getfield #17 // Field age:I 4: ireturn LineNumberTable: line 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/github/hashzhang/basetest/User; } SourceFile: "User.java" Record: long id; descriptor: J java.lang.String name; descriptor: Ljava/lang/String; int age; descriptor: I //以下是 invokedynamic 会调用的方法以及参数信息,我们后面会分析 BootstrapMethods: 0: #50 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 com/github/hashzhang/basetest/User #57 id;name;age #59 REF_getField com/github/hashzhang/basetest/User.id:J #60 REF_getField com/github/hashzhang/basetest/User.name:Ljava/lang/String; #61 REF_getField com/github/hashzhang/basetest/User.age:I InnerClasses: public static final #67= #63 of #65; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
另一种是通过 IDE 的 jclasslib 插件查看,我推荐使用这种方法,查看效果如下: