类的初始化顺序

Posted wengzp

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了类的初始化顺序相关的知识,希望对你有一定的参考价值。

类的初始化

类的初始化就是 JVM 装载类的一个步骤,简单来讲就是执行类构造器

类的初始化顺序

认识类初始化顺序是一个非常重要的事情,可以让我们在开发过程中,减少很多不必要的 BUG。
初始化顺序:

  1. 类中所有属性的默认值。
  2. 父类静态属性,静态块,静态方法(main 方法也是静态方法)的声明(按顺序)。
  3. 子类静态属性,静态块,静态方法(main 方法也是静态方法)的声明(按顺序)。
  4. 父类属性,块,普通方法的声明,构造方法(按顺序)。
  5. 子类属性,块,普通方法的审美,构造方法(按顺序)。
    接下来直接看几个例子,就能有比较直接的理解了。

    例子一

public class Demo {
    public static int i = 8;
    static {
        i = 10;
        System.out.println("父类静态代码块:i = " + i);
    }
    public int j = 88;
    {
        j = 99;
        System.out.println("父类普通代码块:j = " + j);
    }
    public Demo() {
        System.out.println("父类构造方法");
    }
    public static void main(String[] args) {
        System.out.println("main 方法:i = " + i);
        new SubDemo();
                new SubDemo();
    }
}

class SubDemo extends Demo{
    public static int k = 1;
    static {
        k = 5;
        System.out.println("子类静态代码块:k = " + k);
    }
    public int h = 3;
    {
        h = 7;
        System.out.println("子类普通代码块:h = " + h);
    }
    public SubDemo() {
        System.out.println("子类构造方法");
    }
}
输出为:  
父类静态代码块:i = 10  
main 方法:i = 10  
子类静态代码块:k = 5  
父类普通代码块:j = 99  
父类构造方法  
子类普通代码块:h = 7  
子类构造方法  
父类普通代码块:j = 99
父类构造方法
子类普通代码块:h = 7
子类构造方法
  1. 这里调用的是父类的 main 方法,所以,先对父类的所有属性赋默认值,然后对静态属性 i 赋值 8,执行静态代码块,对 i 赋值 10,再打印输出。然后初始化就完成了,接着执行 main 方法中的打印方法。如果不 new 一个子类的实例,到这里就算执行完毕了。
  2. 代码中接着 new 了一个子类的实例。首先我们会对父类进行初始化,但是我们此时父类已经初始化结束,就不会接着再初始化一次了,但是子类还没初始化所以进行子类的初始化。先对所有子类属性进行初始化赋值,然后对 子类属性 k 赋值 1,执行子类静态代码块,对 k 赋值 5,打印输出。到这里,父类和子类的静态属性,代码块,方法就全部初始化完毕了。接着就是非静态的操作了。
  3. 对父类实例属性 j 赋值 88,执行父类实例代码块,对 j 赋值 99 并进行打印,执行父类的构造函数。
  4. 对子类实例属性 h 赋值 3,执行子类实例代码,对 h 赋值 7 并进行打印,执行子类的构造函数。至此就算执行完毕了。
  5. 需要注意的是,静态属性,静态代码块只会在类的装载的时候执行一次,所以当我们再 new 一个子类,也不会打印静态代码块中的内容。

    例子二

    以下代码能正确执行吗?能的话执行结果是多少?
class A
{
    public static int X;
    static { X = B.Y + 1;}
}
public class B
{
    public static int Y = A.X + 1;
    static {}
    public static void main(String[] args) {
        System.out.println("X = "+A.X+", Y = "+B.Y);
    }
}
输出:
X = 1, Y = 2
  1. 这里执行 B 的 main 方法,所以我们会装载 B 类。先对 B 的静态属性进行初始化 Y = 0。然后对 Y 进行赋值。赋值的时候就调用了 A 的静态属性 X,所以会装载 A 类,
  2. 装载 A 类时,先对静态属性 X 初始化赋值 0,再执行静态代码块,此时 B 类的静态属性 Y 是之前的初始化赋值 0,所以这里 X 就为 1。
  3. 所以 Y 就为 2。
    这个题主要是就是要记得,装载类的时候,第一步是属性的初始化。

    例子三

    类初始化过程中,有一个注意的是,当我们在类的静态变量或者静态代码块中调用类的构造函数时,会暂停类的初始化,先进行实例化过程
public class Text {
    public static int k = 0;
    public static Text t1 = new Text("t1");
    public static Text t2 = new Text("t2");
    public static int i = print("i");
    public static int n = 99;
    public int j = print("j");

    {
        print("构造块");
    }
    static {
        print("静态块");
    }

    public Text(String str) {
        System.out.println((++k) + ":" + str + "   i=" + i + "    n=" + n);
        ++i;
        ++n;
    }

    public static int print(String str) {
        System.out.println((++k) + ":" + str + "   i=" + i + "    n=" + n);
        ++n;
        return ++i;
    }

    public static void main(String args[]) {
        Text t = new Text("init");
    }
}
输出:
1:j   i=0    n=0
2:构造块   i=1    n=1
3:t1   i=2    n=2
4:j   i=3    n=3
5:构造块   i=4    n=4
6:t2   i=5    n=5
7:i   i=6    n=6
8:静态块   i=7    n=99
9:j   i=8    n=100
10:构造块   i=9    n=101
11:init   i=10    n=102

这个就是一个暂停类初始化的例子,在对静态属性 t1 赋值时,调用了构造函数,所以会先去对实例属性进行赋值,执行实例代码块,执行构造方法。

写在后面

其实我这里写的类的初始化只是 JVM 加载类的其中一个步骤,JVM 加载类有加载,连接(验证,准备,解析),初始化,使用,卸载几个步骤,想要深入接着了解的,可以弄本 JVM 的书,细细看一下,会有更深的了解。




以上是关于类的初始化顺序的主要内容,如果未能解决你的问题,请参考以下文章

java父子类的初始化顺序--个人总结

JAVA的初始化顺序:

Java编程思想笔记-类的初始化顺序

Java类的初始化顺序

Java类的各种成员初始化顺序如:父子类继承时的静态代码块,普通代码块,静态方法,构造方法,等先后顺

类的初始化顺序