[java] 编译之后的记录类(Record Classes)长什么样子(上)

背景

JEP 395: Records 中提到 JDK 16 \text{JDK 16} JDK 16 正式支持记录类( Record Classes \text{Record Classes} Record Classes)。那么记录类在 class \text{class} class 文件中长什么样子呢?让我们一起来探索吧。

要点

  • 规范构造函数( canonical constructor \text{canonical constructor} canonical constructor)
    • 如果我们在记录类中没有定义构造函数,那么记录类中会出现隐式的 canonical constructor \text{canonical constructor} canonical constructor
  • 对每个 Record Component \text{Record Component} Record Component 而言
    • 记录类中会有一个与之对应的 private \text{private} private, final \text{final} final, 非 static \text{static} static 字段
    • 记录类中会有一个与之对应的被称为 accessor method \text{accessor method} accessor method 的方法

正文

例子

我从 JEP 395: Records 里找了个例子 ⬇️ (略有改动)

java 复制代码
public record Point(int x, int y) {
}

我们将上述代码保存为 Point.java \text{Point.java} Point.java。下方的命令可以编译 Point.java \text{Point.java} Point.java (请注意,所使用的 javac \text{javac} javac 版本应当高于或者等于 16 \text{16} 16)

bash 复制代码
javac Point.java

编译后,会得到 Point.class \text{Point.class} Point.class 文件。使用如下命令可以查看 Point.class \text{Point.class} Point.class 的内容

bash 复制代码
javap -p Point

这个命令在我电脑上运行的结果如下 ⬇️

text 复制代码
Compiled from "Point.java"
public final class Point extends java.lang.Record {
  private final int x;
  private final int y;
  public Point(int, int);
  public final java.lang.String toString();
  public final int hashCode();
  public final boolean equals(java.lang.Object);
  public int x();
  public int y();
}

我画了对应的类图 ⬇️

在反编译的结果中,我们看到了

  • Point \text{Point} Point 的构造函数
  • x x x 字段和 y y y 字段
  • x ( ) x() x() 方法和 y ( ) y() y() 方法

但是我们在 Point.java \text{Point.java} Point.java 中 并没有 显式定义这些内容,那么它们是从哪里来的呢?

规范构造函数(Canonical Constructor

如果我们在记录类没有显式定义构造函数,那么记录类中会出现隐式的 canonical constructor \text{canonical constructor} canonical constructor。 8.10.4. Record Constructor Declarations 提到了相关细节 ⬇️

可以用如下命令查看 Point.class \text{Point.class} Point.class 的详细内容

bash 复制代码
javap -v -p Point

完整的结果比较长,其中和构造函数相关的部分如下

text 复制代码
  public Point(int, int);
    descriptor: (II)V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Record."<init>":()V
         4: aload_0
         5: iload_1
         6: putfield      #7                  // Field x:I
         9: aload_0
        10: iload_2
        11: putfield      #13                 // Field y:I
        14: return
      LineNumberTable:
        line 1: 0
    MethodParameters:
      Name                           Flags
      x
      y

我们可以手动对其进行反编译 ⬇️

java 复制代码
// 以下内容是我手动反编译的结果,仅供参考
public Record(int x, int y) {
    super();
    this.x = x;
    this.y = y;
}

Record Components

8.10. Record Classes 中提到了 Record Components \text{Record Components} Record Components ⬇️ (重要的部分我用绿色框和绿色箭头标了出来)

Point \text{Point} Point 这个记录类而言,以下两者都是它的 Record Component \text{Record Component} Record Component

  • int x \text{int x} int x
  • int y \text{int y} int y

8.10.3. Record Members 提到了如下内容 ⬇️ (重要的部分我用绿色框标了出来)

对每个 Record Component \text{Record Component} Record Component 而言,

  • 记录类中会有一个与之对应的 private \text{private} private, final \text{final} final, 非 static \text{static} static 字段
  • 记录类中会有一个与之对应的被称为 accessor method \text{accessor method} accessor method 的方法

我们可以用如下的命令查看 Point.class \text{Point.class} Point.class 的详细内容

bash 复制代码
javap -v -p Point

完整的结果比较长,其中和 Record Components \text{Record Components} Record Components 相关的部分如下

text 复制代码
  private final int x;
    descriptor: I
    flags: (0x0012) ACC_PRIVATE, ACC_FINAL

  private final int y;
    descriptor: I
    flags: (0x0012) ACC_PRIVATE, ACC_FINAL
    
  public int x();
    descriptor: ()I
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #7                  // Field x:I
         4: ireturn
      LineNumberTable:
        line 1: 0

  public int y();
    descriptor: ()I
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #13                 // Field y:I
         4: ireturn
      LineNumberTable:
        line 1: 0

我们可以手动对其进行反编译 ⬇️

java 复制代码
// 以下内容是我手动反编译的结果,仅供参考
private final int x;
private final int y;

public int x() {
    return this.x;
}

public int y() {
    return this.y;
}

参考资料

其他

Point 的类图所用到的代码

puml 复制代码
@startuml
'https://plantuml.com/class-diagram

@startuml

title <i>Point</i> 的类图
caption \n\n
' caption 的内容只是为了防止掘金平台生成的水印遮盖图中的文字

abstract java.lang.Record
class Point
java.lang.Record <-- Point

abstract java.lang.Record {
    # Record()
    + {abstract} boolean equals(Object)
    + {abstract} int hashCode()
    + {abstract} String toString()
}

class Point {
  - final int x
  - final int y
  + Point(int, int)
  + final String toString()
  + final int hashCode()
  + final boolean equals(Object)
  + int x()
  + int y()
}

note left of java.lang.Record::equals
override 了 <i>java.lang.Object</i> 中的方法
end note

note left of java.lang.Record::hashCode
override 了 <i>java.lang.Object</i> 中的方法
end note

note left of java.lang.Record::toString
override 了 <i>java.lang.Object</i> 中的方法
end note

@enduml
相关推荐
烂蜻蜓1 小时前
Flask入门教程(十二):表单处理——从基础到Flask-WTF完整实践
后端·python·flask
2601_962055978 小时前
跟据spring boot版本,查看对应的tomcat,并查看可支持的tomcat的版本范围
spring boot·后端·tomcat
EXI-小洲8 小时前
Java 操作 Word:字符串替换、图片插入、动态生成表格与API接口下载
java·开发语言·spring boot·word
2601_9619017010 小时前
SpringBoot使用Nacos进行application.yml配置管理
java·spring boot·spring
Lost of 程序猿10 小时前
ASP.NET Core API 幂等性设计深度实战:从一次重复申领事故说起
后端·asp.net
何以解忧,唯有..10 小时前
LangChain 工具调用(Tool Calling)实战指南
java·前端·langchain
QQ_216962909611 小时前
【源码编号:project79475】SpringBoot校内二手交易平台:商品发布、分类检索、留言交流、订单管理全流程实战
java·spring boot·后端
前端 贾公子12 小时前
第09章:上下文与记忆 (2)
java·服务器·前端
2601_9622035112 小时前
Java进阶07集合(续)
java·开发语言
郑州光合科技余经理12 小时前
本地生活服务系统:模块边界与结算字段怎么拆
java·开发语言·前端·后端·系统架构·uni-app·php