我们先看一个例子:
class A3{ B3 b3 = new B3(); static C3 c4 = new C3(); static{ System.out.println("A3"); }
public A3() {
System.out.println("A3-init");
}
}
class B3{ static{ System.out.println("B3"); } } class C3{ static{ System.out.println("C3"); } } public class StaticTest { public static void main(String[] args) { A3 a3 = new A3(); } }
现在看一下A3的字节码 clinit
0 new #5 <com/suning/mxz/TestJVM/C3> 静态成员变量先初始化,也说明了会先load这个class
3 dup
4 invokespecial #6 <com/suning/mxz/TestJVM/C3.<init>>
7 putstatic #7 <com/suning/mxz/TestJVM/A3.c4>
10 getstatic #8 <java/lang/System.out>
13 ldc #9 <A3>
15 invokevirtual #10 <java/io/PrintStream.println>
18 return
init
0 aload_0 1 invokespecial #1 <java/lang/Object.<init>> 4 aload_0 5 new #2 <com/suning/mxz/TestJVM/B3> 8 dup 9 invokespecial #3 <com/suning/mxz/TestJVM/B3.<init>> 12 putfield #4 <com/suning/mxz/TestJVM/A3.b3> 15 getstatic #5 <java/lang/System.out> 18 ldc #6 <A3-init> 20 invokevirtual #7 <java/io/PrintStream.println> 23 return
从上面可以看出来,在A3的初始化字节码里首先是执行Object的构造方法,然后才是成员变量的构造方法,最后才是自己的构造方法里的逻辑
==============================2020-12-25=====================
1 类加载的最后一步是初始化,这里说的初始化是指clinit,注意可不是执行实例变量的构造方法,clinit所做的就是执行static代码块和static的变量。所以,能看到clinit的字节码就干了两件事。一个是new一个C3并赋值给c4这个引用。一个是执行static代码块
2 然后是当执行main方法中的 A3 a3 = new A3();
此时的init方法字节码的顺序是
1 执行Object的init,注意这个init是字节码的init,可不是直接的Object的构造方法
2 初始化成员变量,这里是b3
3 然后执行A3的构造方法
注意观察B3的执行可以总结如下
4 aload_0 5 new #2 <com/suning/mxz/TestJVM/B3> new 8 dup 9 invokespecial #3 <com/suning/mxz/TestJVM/B3.<init>> 执行init 12 putfield #4 <com/suning/mxz/TestJVM/A3.b3> 赋值
1 new 分配空间
2 执行字节码里的init
3 把对象的地址赋值给引用
在这里也可以看得出来,当new一个对象的时候,不单单是执行构造方法。字节码里的init的范围是比我们写的构造方法要大的。因为还要初始化成员变量。
在这里再抛出来一个问题,成员变量如果没有初始化字节码是怎么样的
public class Account { private int id ;
0 aload_0 1 invokespecial #1 <java/lang/Object.<init>> 4 return
能够看得出来,没有对id做任何的操作。而且代码中没有写构造方法,所以在这里直接执行Object的init之后就返回了