true or false?

2023-05-11 17:36:30 true false

有同学在星球问了这样一个问题。

代码是这样的:

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. 1. public com.aixiaoxian.orm.Main() 调用无参构造函数

  2. 2. public static void main(java.lang.String[]); main 方法

  3. 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:
       : 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。
      11return

  public static void main(java.lang.String[]);
    Code:
       : 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,               // InvokeDynamic #0:makeConcatWithConstants:(ZZ)Ljava/lang/String; 动态调用makeConcatWithConstants()方法
      17: invokevirtual #37                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 调用PrintStream.println()方法
      20return

  static {};
    Code:
       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中。
      22return
}

那么问题是为什么 b 在初始化的时候为什么没有把 a 的值赋给他呢?

看这个问题我们先复习一下 new 对象的过程。

当虚拟机遇见new关键字时候,实现判断当前类是否已经加载,如果类没有加载,首先执行类的加载机制,加载完成后再为对象分配空间、初始化等。

  1. 1. 首先校验当前类是否被加载,如果没有加载,执行类加载机制

  2. 2. 加载:就是从字节码加载成二进制流的过程

  3. 3. 验证:当然加载完成之后,当然需要校验Class文件是否符合虚拟机规范,跟我们接口请求一样,件事情当然是先做个参数校验了

  4. 4. 准备:为静态变量、常量赋默认值

  5. 5. 解析:把常量池中符号引用(以符号描述引用的目标)替换为直接引用(指向目标的指针或者句柄等)的过程

  6. 6. 初始化:执行static代码块(cinit)进行初始化,如果存在父类,先对父类进行初始化

当类加载完成之后,紧接着就是对象分配内存空间和初始化的过程

  1. 1. 首先为对象分配合适大小的内存空间

  2. 2. 接着为实例变量赋默认值

  3. 3. 设置对象的头信息,对象hash码、GC分代年龄、元数据信息等

  4. 4. 执行构造函数(init)初始化

在这个代码中,首先先要执行类的初始化,类初始化过程中去给静态变量、常量赋值,之后再去给实例变量赋值,按照道理来说 b 应该也是 true 才对。

问题其实出现在代码顺序上,行代码就是去 new 一个静态的 Main 实例对象,但是这里代码顺序先去初始化 instance ,但是此时代码 a 的定义写在 instance 之后,所以初始化 instance 对象的时候其实 a 还没有赋值,所以给 b 赋值的时候就是 false。

所以这里的代码只要调整一下顺序,把private static boolean a = initA();放到行,结果就会变成 true 了。

好了好了,就这样,都散了吧。


相关文章