[Java] 如何理解 class 文件中字段的 access flags?

背景

在日常开发过程中,我们有时候需要查看 class \text{class} class 文件的内容。如果我们对 class \text{class} class 文件的结构有基本的了解,那么就会事半功倍。由于这个话题很大,而且我自己的水平也有限,所以每次只写一个很小的主题。本文的主题是理解 class \text{class} class 文件中字段的 access flags \text{access flags} access flags。

要点

目前(JDK26), class \text{class} class 文件中字段的 access flags \text{access flags} access flags 一共支持 9 9 9 种类型。对每一种类型,本文都提供了对应的代码进行验证。

正文

The Java® Virtual Machine Specification 中的 4.5. Fields 小节详细介绍了 class \text{class} class 文件中 field_info \text{field\_info} field_info 的结构,它的开头是这样的 ⬇️

u2 \text{u2} u2 的含义

The Java® Virtual Machine Specification 中的 Chapter 4. The class File Format 开头解释了 u2 \text{u2} u2 的含义 ⬇️ (如下图红色框所示)

u2 \text{u2} u2 表示 2 byte \text{2 byte} 2 byte 的无符号数

字段的 access flags \text{access flags} access flags

The Java® Virtual Machine Specification 中的 4.5. Fields 小节的开头提供了一个表格 ⬇️ (表格的名称是 Table 4.5-A. Field access and property flags

Flag Name Value Interpretation
ACC_PUBLIC 0x0001 Declared public; may be accessed from outside its package.
ACC_PRIVATE 0x0002 Declared private; accessible only within the defining class and other classes belonging to the same nest (§5.4.4).
ACC_PROTECTED 0x0004 Declared protected; may be accessed within subclasses.
ACC_STATIC 0x0008 Declared static.
ACC_FINAL 0x0010 Declared final; never directly assigned to after object construction (JLS §17.5).
ACC_VOLATILE 0x0040 Declared volatile; cannot be cached.
ACC_TRANSIENT 0x0080 Declared transient; not written or read by a persistent object manager.
ACC_SYNTHETIC 0x1000 Declared synthetic; not present in the source code.
ACC_ENUM 0x4000 Declared as an element of an enum class.

表格中一共列举了 9 \text{9} 9 种情况。我们分别来看。

7 \text{7} 7 种情况

请将以下代码保存为 Simple.java \text{Simple.java} Simple.java

java 复制代码
public class Simple {
  public int f1;
  private int f2;
  protected int f3;
  static int f4;
  final int f5 = 1;
  volatile int f6;
  transient int f7;
}

使用如下的命令可以编译 Simple.java \text{Simple.java} Simple.java

bash 复制代码
javac Simple.java

编译之后,当前目录会多出一个名为 Simple.class \text{Simple.class} Simple.class 的文件。使用如下的命令可以查看 Simple.class \text{Simple.class} Simple.class 的详细内容

bash 复制代码
javap -v -p Simple

完整的结果略有点长,我把和本文相关的部分复制到下方了 ⬇️ (... 表示本文不关心的内容)

text 复制代码
...
public class Simple
...
{
  public int f1;
    descriptor: I
    flags: (0x0001) ACC_PUBLIC

  private int f2;
    descriptor: I
    flags: (0x0002) ACC_PRIVATE

  protected int f3;
    descriptor: I
    flags: (0x0004) ACC_PROTECTED

  static int f4;
    descriptor: I
    flags: (0x0008) ACC_STATIC

  final int f5;
    descriptor: I
    flags: (0x0010) ACC_FINAL
    ConstantValue: int 1

  volatile int f6;
    descriptor: I
    flags: (0x0040) ACC_VOLATILE

  transient int f7;
    descriptor: I
    flags: (0x0080) ACC_TRANSIENT

  public Simple();
    ...
}
...

不难验证,前 7 \text{7} 7 种情况都出现了。

8 \text{8} 8 种情况: ACC_SYNTHETIC \text{ACC\_SYNTHETIC} ACC_SYNTHETIC

为了看到这种情况,我们需要生成合成字段。考虑到创建内部类的实例时,内部类的对象会持有宿主类的引用,而这个引用会保存在内部类的合成字段中。

请将以下代码保存为 Case8.java \text{Case8.java} Case8.java

java 复制代码
class Case8 {
  int a;
  class Inner {
    int b = a;
  }
}

使用如下的命令可以编译 Case8.java \text{Case8.java} Case8.java

bash 复制代码
javac -parameters Case8.java

编译之后,当前目录会多出两个文件 ⬇️

  • Case8.class
  • Case8$Inner.class

使用如下命令可以查看 Case8$Inner.class 的详细内容

bash 复制代码
javap -v -p Case8\$Inner

完整的结果略有点长,我把和本文相关的部分复制到下方了 ⬇️ (... 表示本文不关心的内容)

text 复制代码
...
class Case8$Inner
  ...
{
  int b;
    descriptor: I
    flags: (0x0000)

  final Case8 this$0;
    descriptor: LCase8;
    flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC

  Case8$Inner(Case8);
    ...
}
...

可以看到 Case8$Inner 这个类中的确有一个合成字段 this$0。借助 javap \text{javap} javap 命令,我把以下两个 class \text{class} class 文件手动反编译了一下

  • Case8.class
  • Case8$Inner.class

结果如下 ⬇️

java 复制代码
// 以下代码是我手动反编译的结果,内容仅供参考,可能有不准确甚至错误的地方

class Case8 {
    int a;
    Case8() {
        super();
    }
}

class Case8$Inner {
    int b;
    // 下方这个字段是合成字段,在 java 代码中看不到。
    final Case8 this$0;
    Case8$Inner(Case8 this$0) {
        this.this$0 = this$0;
        super();
        this.b = this.this$0.a;
    }
}

简要的类图如下

9 \text{9} 9 种情况: ACC_ENUM \text{ACC\_ENUM} ACC_ENUM

9 \text{9} 9 种情况与枚举类型有关。我们写个枚举类来验证一下。请将以下代码保存为 Direction.java \text{Direction.java} Direction.java

java 复制代码
public enum Direction {
  EAST,
  WEST,
  SOUTH,
  NORTH;
}

使用如下的命令可以编译 Direction.java \text{Direction.java} Direction.java

bash 复制代码
javac Direction.java

编译之后,当前目录会多出名为 t e x t D i r e c t i o n . c l a s s text{Direction.class} textDirection.class 的文件。使用如下命令可以查看 Direction.class \text{Direction.class} Direction.class 的详细内容

bash 复制代码
javap -v -p Direction

完整的结果略有点长,我把和本文相关的部分复制到下方了 ⬇️ (... 表示本文不关心的内容)

text 复制代码
...
public final class Direction extends java.lang.Enum<Direction>
...
{
  public static final Direction EAST;
    descriptor: LDirection;
    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM

  public static final Direction WEST;
    descriptor: LDirection;
    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM

  public static final Direction SOUTH;
    descriptor: LDirection;
    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM

  public static final Direction NORTH;
    descriptor: LDirection;
    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM

  private static final Direction[] $VALUES;
    descriptor: [LDirection;
    flags: (0x101a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC

...
}
...

不难验证, Direction \text{Direction} Direction 中定义的 4 4 4 个枚举值(即, EAST, WEST, SOUTH, NORTH \text{EAST, WEST, SOUTH, NORTH} EAST, WEST, SOUTH, NORTH),在 class \text{class} class 文件中,其实是 4 4 4 个静态字段,而且这 4 4 4 个字段的 access flags \text{access flags} access flags 中, ACC_ENUM \text{ACC\_ENUM} ACC_ENUM 都处于置位状态(也就是说这一位的值是 1 1 1)。至于 java \text{java} java 语言中,实现枚举类型的更多细节,可以参考我之前写的一篇文章:Java 浅析枚举的实现

参考资料

The Java® Virtual Machine Specification 中的

其他

"简要的类图"是怎么画出来的

我用了 IntelliJ IDEA (Community Edition)PlantUML 的插件来绘制那张图,用到的代码如下 ⬇️

puml 复制代码
@startuml

title 简要的类图
caption \n\n
' caption 的内容是为了防止掘金平台自动生成的水印遮盖图中的文字

class Case8 {
    int a
    Case8()
}

class Case8$Inner {
    int b
    final Case8 this$0
    Case8$Inner(Case8 this$0)
}

note left of Case8$Inner::Case8$Inner
可以认为构造函数的代码是这样的 <:point_down:>

<code>
Case8$Inner(Case8 this$0) {
    this.this$0 = this$0;
    super();
    this.b = this.this$0.a;
}
</code>
end note

@enduml
相关推荐
腻害兔8 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
GetcharZp9 小时前
抛弃低效SSH!这套Ansible+Web可视化神器,让你天天准点下班!
后端
码智社9 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
萧瑟余晖10 小时前
JDK 26 新特性详解
java·开发语言
马优晨11 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
weixin_4462608512 小时前
HACO:面向动态部署环境的对冲式智能计算可靠多智能体调度框架
后端·python·flask
ttwuai13 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
用户83562907805113 小时前
Python 实现 Excel 页面布局与打印设置自动化
后端·python
维天说13 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json