有同学在星球问了这样一个问题。
代码是这样的:
public class Main {
private static final Main instance = new Main();
private boolean b = a;
private static boolean a = initA();
private static boolean c = a;
private static boolean initA() {
return true;
}
private static boolean getC() {
return c;
}
public static void main(String[] args) {
System.out.println(instance.b + " " + getC());
}
}
最后的输出结果你以为是 true true,其实是 false true。
GPT 表示对于这个问题,他确实无法理解。。。
为了看这个问题的结果,可以反编译看下,执行命令:
javac Main.java
javap -c Main
得到反编译的字节码结果,结果包含 3 个部分:
- 1. public com.aixiaoxian.orm.Main() 调用无参构造函数
- 2. public static void main(java.lang.String[]); main 方法
- 3. static{} 静态代码
看静态代码部分赋值,13、16 、19,可以很明显的发现 c 和 a 的值都是 true。
然后再看 8 和 6,取值是索引 13 的位置,并没有初始化,所以值应该默认是 false。
Compiled from "Main.java"
public class com.aixiaoxian.orm.Main {
public com.aixiaoxian.orm.Main();
Code:
0: aload_0 //将当前对象的引用(this)压入操作数栈。
1: invokespecial #1 // Method java/lang/Object."<init>":()V 调用构造函数
4: aload_0 // 再次将当前对象的引用(this)压入操作数栈。
5: getstatic #7 // Field a:Z //获取静态变量a的值,并将其压入操作数栈中。
8: putfield #13 // Field b:Z // 将操作数栈顶的值赋给成员变量b。
11: return
public static void main(java.lang.String[]);
Code:
0: getstatic #19 // Field java/lang/System.out:Ljava/io/PrintStream; 获取System.out静态变量
3: getstatic #25 // Field instance:Lcom/aixiaoxian/orm/Main; 获取Main类的静态变量instance
6: getfield #13 // Field b:Z 将instance对象的成员变量b的值(布尔型)压入操作数栈
9: invokestatic #29 // Method getC:()Z 调用getC()方法
12: invokedynamic #33, 0 // InvokeDynamic #0:makeConcatWithConstants:(ZZ)Ljava/lang/String; 动态调用makeConcatWithConstants()方法
17: invokevirtual #37 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 调用PrintStream.println()方法
20: return
static {};
Code:
0: new #8 // class com/aixiaoxian/orm/Main 创建Main类的新实例
3: dup: //复制对象引用,将新实例的引用压入操作数栈
4: invokespecial #43 // Method "<init>":()V 调用Main类的构造方法
7: putstatic #25 // Field instance:Lcom/aixiaoxian/orm/Main; 给instance静态变量赋值,将栈顶元素存储在instance中
10: invokestatic #44 // Method initA:()Z 调用initA()方法
13: putstatic #7 // Field a:Z 给a静态变量赋值
16: getstatic #7 // Field a:Z 获取a静态变量的值,将其压入操作数栈。
19: putstatic #16 // Field c:Z 给c静态变量赋值,将栈顶元素(即a的值)存储在c中。
22: return
}
那么问题是为什么 b 在初始化的时候为什么没有把 a 的值赋给他呢?
看这个问题我们先复习一下 new 对象的过程。
当虚拟机遇见new关键字时候,实现判断当前类是否已经加载,如果类没有加载,首先执行类的加载机制,加载完成后再为对象分配空间、初始化等。
- 1. 首先校验当前类是否被加载,如果没有加载,执行类加载机制
- 2. 加载:就是从字节码加载成二进制流的过程
- 3. 验证:当然加载完成之后,当然需要校验Class文件是否符合虚拟机规范,跟我们接口请求一样,第一件事情当然是先做个参数校验了
- 4. 准备:为静态变量、常量赋默认值
- 5. 解析:把常量池中符号引用(以符号描述引用的目标)替换为直接引用(指向目标的指针或者句柄等)的过程
- 6. 初始化:执行static代码块(cinit)进行初始化,如果存在父类,先对父类进行初始化
当类加载完成之后,紧接着就是对象分配内存空间和初始化的过程
- 1. 首先为对象分配合适大小的内存空间
- 2. 接着为实例变量赋默认值
- 3. 设置对象的头信息,对象hash码、GC分代年龄、元数据信息等
- 4. 执行构造函数(init)初始化
在这个代码中,首先先要执行类的初始化,类初始化过程中去给静态变量、常量赋值,之后再去给实例变量赋值,按照道理来说 b 应该也是 true 才对。
问题其实出现在代码顺序上,第一行代码就是去 new 一个静态的 Main 实例对象,但是这里代码顺序先去初始化 instance ,但是此时代码 a 的定义写在 instance 之后,所以初始化 instance 对象的时候其实 a 还没有赋值,所以给 b 赋值的时候就是 false。
所以这里的代码只要调整一下顺序,把private static boolean a = initA();
放到第一行,结果就会变成 true 了。
好了好了,就这样,都散了吧。