1.类的初始化时机(含超出基础知识)
- 第一种:遇到new、getstatic、putstatic、invokestatic这四条字节码指令时,如果类还没有进行过初始化,则需要先触发其初始化。
生成这四条指令最常见的Java代码场景是:使用new关键字实例化对象时、读取或设置一个类的静态字段(static)时(被static修饰又被final修饰的,已在编译期把结果放入常量池的静态字段除外)、以及调用一个类的静态方法时。 - 第二种:使用Java.lang.refect包的方法对类进行反射调用时,如果类还没有进行过初始化,则需要先触发其初始化。
- 第三种:当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
- 第四种:当虚拟机启动时,用户需要指定一个要执行的主类,虚拟机会先执行该主类。
- 第五种:当使用JDK1.5支持时,如果一个java.langl.incoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化
2.类初始化的顺序
总的来说,静态修饰优先,子父类继承父类优先
父类(静态变量、静态代码块)–>子类(静态变量、静态代码块)–>父类(变量、代码块)–> 父类构造器–>子类(变量、初始化块)–>子类构造器。注意:静态代码和静态变量同级,变量和代码块同级。谁在前先执行谁。类只会初始化一次。
补充:静态初始化,是在加载类的时候初始化。而非静态初始化,是new类实例对象的时候加载。
public class Test01 {
public static void main(String[] args) {
new Child("mike");
}
}
class People {
private String name;
public People() {
System.out.print("1");
}
public People(String name) {
System.out.print("2");
this.name = name;
}
}
class Child extends People {
People father;
public Child(String name) {
System.out.print("3");
father = new People(name + " F");
}
public Child() {
System.out.print("4");
}
}
上面案例分析中,在初始化子类时,优先加载静态资源(String name),下一步调用父类空参构造器,(隐含代码 super(),在子类构造器第一行),然后调用子类构造器,最后调用父类有参构造。
最终结果为132
public class Test03 {
public static void main(String[] args) {
new A(new B());
}
}
class A {
public A() {
System.out.println("A");
}
public A(B b) {
this();
System.out.println("AB");
}
}
class B {
public B() {
System.out.println("B");
}
}
上述案例中,参数列表优先于方法本身调用!!!
综上所述:
静态优先与非静态
父类优先于子类
方法调用对象优先于方法
4.静态属性赋值右侧为本类对象
注意:当加载到一个静态属性的时候他的赋值对象为一个静态的对象,这个时候就会中断静态相关的加载,转而先去执行非静态相关的代码。这里还需要注意的是属性和代码块的加载遵循他们的先后出场顺序。``
package classloader;
public class Text {
//1)执行main方法
public static void main(String[] args) {
// 24)执行main方法中的代码,直接调用构造方法
Text t = new Text("init");
}
//2)首先执行main方法所在类的静态相关代码, k = 0;
public static int k = 0;
//3)这里的静态属性赋有一个非静态的对象 所以停止类加载转向所有的非静态初始化
public static Text t1 = new Text("t1");
// 9)由于上一句静态代码以及相关的非静态代码执行完毕所以来到了下一句静态代码的执行
public static Text t2 = new Text("t2");
// 15) 由于上一句静态代码以及相关的非静态代码执行完毕所以来到了下一句静态代码的执行
public static int i = print("i");
//17) 由于上一句静态代码以及相关的非静态代码执行完毕所以来到了下一句静态代码的执行
public static int n = 99;
// 4)这是第一个非静态属性它的赋值调用print("j")方法
// 10)原理和3)的执行过程类似,调用print("j")
// 20)执行非静态代码,调用print("j")
public int j = print("j");
{
// 6)现在4)处的非静态属性终于初始化完毕,所以接着来到了非静态块
// 12)现在10)处的非静态属性初始化完毕,执行非静态代码块
// 22)执行非静态代码块
print("构造块");
}
static {
//18)执行该静态代码块
print("静态块");
}
// 8)非静态块执行完毕之后来到了构造方法(它本身也是非静态的)此时k = 2, i = 2, n = 2, 打印 3 : t1 i = 2 n = 2;
// 14) 非静态块执行完毕之后来到了构造方法(它本身也是非静态的)此时k = 5, i = 5, n = 5, 打印 6 : t2 i = 5 n =5
// 25) 类加载完毕,直接调用构造方法,此时k = 10, i = 10, n = 102,打印 11 : init i = 10 n = 102
public Text(String str) {
System.out.println((++k) + ":" + str + " i = " + i + " n = " + n);
++i;
++n;
}
// 5)来自4)的调用此时k = 0, i = 0, n = 0, 打印 1 : j i = 0 n = 0
// 7)来自6)的调用此时k = 1, i = 1, n = 1, 打印 2 : 构造块 i = 1 n = 1
// 11) 来自10)的调用此时 k = 3, i = 3, n = 3, 打印 4 : j i = 3 n = 3
// 13)来自12)的调用此时 k = 4, i = 4, n = 4, 打印 5 : 构造块 i = 4 n = 4
// 16)来自15)的调用此时 k = 6, i = 6, n = 6, 打印 7 : i i = 6 n = 6
// 19)来自18)的调用此时 k = 7, i = 7, n = 99,打印 8 : 静态块 i = 7 n = 99
// 21)来自20)的调用此时 k = 8, i = 8, n = 100,打印 9 : j i = 8 n = 100
// 23)来自22)的调用此时 k = 9, i = 9, n = 101,打印 10 : 构造快 i = 9 n = 101
public static int print(String str) {
System.out.println((++k) + ":" + str + " i = " + i + " n = " + n);
++n;
return ++i;
}
}