Java Record 的一些思考 - 默认方法使用以及基于预编译生成相关字节码的底层实现(上)

简介: Java Record 的一些思考 - 默认方法使用以及基于预编译生成相关字节码的底层实现(上)

快速上手 Record 类


我们先举一个简单例子,声明一个用户 Record。

public record User(long id, String name, int age) {}

这样编写代码之后,Record 类默认包含的元素和方法实现包括:

  1. record 头指定的组成元素(int id, String name, int age),并且,这些元素都是 final 的。
  2. record 默认只有一个构造器,是包含所有元素的构造器。
  3. record 的每个元素都有一个对应的 getter(但这种 getter 并不是 getxxx(),而是直接用变量名命名,所以使用序列化框架,DAO 框架都要注意这一点)
  4. 实现好的 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 插件查看,我推荐使用这种方法,查看效果如下:



image.png

相关文章
|
1月前
|
消息中间件 Java Kafka
在Java中实现分布式事务的常用框架和方法
总之,选择合适的分布式事务框架和方法需要综合考虑业务需求、性能、复杂度等因素。不同的框架和方法都有其特点和适用场景,需要根据具体情况进行评估和选择。同时,随着技术的不断发展,分布式事务的解决方案也在不断更新和完善,以更好地满足业务的需求。你还可以进一步深入研究和了解这些框架和方法,以便在实际应用中更好地实现分布式事务管理。
|
1月前
|
Java
java小工具util系列5:java文件相关操作工具,包括读取服务器路径下文件,删除文件及子文件,删除文件夹等方法
java小工具util系列5:java文件相关操作工具,包括读取服务器路径下文件,删除文件及子文件,删除文件夹等方法
74 9
|
1月前
|
Java
轻松上手Java字节码编辑:IDEA插件VisualClassBytes全方位解析
本插件VisualClassBytes可修改class字节码,包括class信息、字段信息、内部类,常量池和方法等。
120 6
|
1月前
|
安全 Java 开发者
Java中WAIT和NOTIFY方法必须在同步块中调用的原因
在Java多线程编程中,`wait()`和`notify()`方法是实现线程间协作的关键。这两个方法必须在同步块或同步方法中调用,这一要求背后有着深刻的原因。本文将深入探讨为什么`wait()`和`notify()`方法必须在同步块中调用,以及这一机制如何确保线程安全和避免死锁。
41 4
|
1月前
|
Java
深入探讨Java中的中断机制:INTERRUPTED和ISINTERRUPTED方法详解
在Java多线程编程中,中断机制是协调线程行为的重要手段。了解和正确使用中断机制对于编写高效、可靠的并发程序至关重要。本文将深入探讨Java中的`Thread.interrupted()`和`Thread.isInterrupted()`方法的区别及其应用场景。
37 4
|
28天前
|
Java 数据处理 数据安全/隐私保护
Java处理数据接口方法
Java处理数据接口方法
26 1
|
2月前
|
Java API
Java 对象释放与 finalize 方法
关于 Java 对象释放的疑惑解答,以及 finalize 方法的相关知识。
54 17
|
1月前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
135 4
|
1月前
|
Java 测试技术 Maven
Java一分钟之-PowerMock:静态方法与私有方法测试
通过本文的详细介绍,您可以使用PowerMock轻松地测试Java代码中的静态方法和私有方法。PowerMock通过扩展Mockito,提供了强大的功能,帮助开发者在复杂的测试场景中保持高效和准确的单元测试。希望本文对您的Java单元测试有所帮助。
227 2
|
1月前
|
Java 数据格式 索引
使用 Java 字节码工具检查类文件完整性的原理是什么
Java字节码工具通过解析和分析类文件的字节码,检查其结构和内容是否符合Java虚拟机规范,确保类文件的完整性和合法性,防止恶意代码或损坏的类文件影响程序运行。
50 5