这个面试题是真的坑爹

文章讨论了一段Java代码中由于静态变量初始化顺序引起的误解。在代码中,静态变量a的初始化发生在实例变量b之后,导致b没有被正确地赋予a的值。通过反编译字节码和解释类加载及对象初始化过程,解释了b为false而非true的原因,并指出调整代码顺序可以修正此问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

代码是这样的:


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 个部分:

  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。


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关键字时候,实现判断当前类是否已经加载,如果类没有加载,首先执行类的加载机制,加载完成后再为对象分配空间、初始化等。

  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 了。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值