有同学在星球问了这样一个问题。
代码是这样的:
java
复制代码
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 表示对于这个问题,他确实无法理解。。。
为了看这个问题的结果,可以反编译看下,执行命令:
java
复制代码
javac Main.java javap -c Main
得到反编译的字节码结果,结果包含 3 个部分:
- public com.aixiaoxian.orm.Main() 调用无参构造函数
- public static void main(java.lang.String[]); main 方法
- static{} 静态代码
看静态代码部分赋值,13、16 、19,可以很明显的发现 c 和 a 的值都是 true。
然后再看 8 和 6,取值是索引 13 的位置,并没有初始化,所以值应该默认是 false。
java
复制代码
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关键字时候,实现判断当前类是否已经加载,如果类没有加载,首先执行类的加载机制,加载完成后再为对象分配空间、初始化等。
- 首先校验当前类是否被加载,如果没有加载,执行类加载机制
- 加载:就是从字节码加载成二进制流的过程
- 验证:当然加载完成之后,当然需要校验Class文件是否符合虚拟机规范,跟我们接口请求一样,第一件事情当然是先做个参数校验了
- 准备:为静态变量、常量赋默认值
- 解析:把常量池中符号引用(以符号描述引用的目标)替换为直接引用(指向目标的指针或者句柄等)的过程
- 初始化:执行static代码块(cinit)进行初始化,如果存在父类,先对父类进行初始化
当类加载完成之后,紧接着就是对象分配内存空间和初始化的过程
- 首先为对象分配合适大小的内存空间
- 接着为实例变量赋默认值
- 设置对象的头信息,对象hash码、GC分代年龄、元数据信息等
- 执行构造函数(init)初始化
在这个代码中,首先先要执行类的初始化,类初始化过程中去给静态变量、常量赋值,之后再去给实例变量赋值,按照道理来说 b 应该也是 true 才对。
问题其实出现在代码顺序上,第一行代码就是去 new 一个静态的 Main 实例对象,但是这里代码顺序先去初始化 instance ,但是此时代码 a 的定义写在 instance 之后,所以初始化 instance 对象的时候其实 a 还没有赋值,所以给 b 赋值的时候就是 false。
所以这里的代码只要调整一下顺序,把private static boolean a = initA();
放到第一行,结果就会变成 true 了。
好了好了,就这样,都散了吧。