Java中的类加载顺序

来源:互联网 发布:mac笔记本电脑价格 编辑:程序博客网 时间:2024/05/16 19:32

今天来研究一下Java中类的加载顺序。

一般情况:

Demo1

首先看父类:

public class Father {    static {        System.out.println("这是 Father 的静态代码块");    }    {        System.out.println("这是 Father 的代码块");    }    public Father() {        System.out.println("这是 Father 的构造方法");    }}

再看子类:

public class Son extends Father{    static {        System.out.println("这是 Son 的静态代码块");    }    {        System.out.println("这是 Son 的代码块");    }    public Son(){        System.out.println("这是 Son 的构造方法");    }}

然后调用代码:

Son son = new Son();

运行结果如下:

这是 Father 的静态代码块这是 Son 的静态代码块这是 Father 的代码块这是 Father 的构造方法这是 Son 的代码块这是 Son 的构造方法

根据以上过程,其实我们可以发现其中的规律了:
1.类加载的时候,如果有父类,会先初始化父类。
2.如果有静态的,就先加载静态的,然后再加载非静态的。

Demo2

把父类和子类稍作修改,分别加了一个静态的成员变量。
父类:

public class Father {    private static Father father = new Father();    static {        System.out.println("这是 Father 的静态代码块");    }    {        System.out.println("这是 Father 的代码块");    }    public Father() {        System.out.println("这是 Father 的构造方法");    }}

子类:

public class Son extends Father{    private static Son son = new Son();    public Son(){        System.out.println("这是 Son 的构造方法");    }    static {        System.out.println("这是 Son 的静态代码块");    }    {        System.out.println("这是 Son 的代码块");    }}

同样调用代码:

Son son = new Son();

运行结果如下:

这是 Father 的代码块这是 Father 的构造方法这是 Father 的静态代码块这是 Father 的代码块这是 Father 的构造方法这是 Son 的代码块这是 Son 的构造方法这是 Son 的静态代码块这是 Father 的代码块这是 Father 的构造方法这是 Son 的代码块这是 Son 的构造方法

这次结果和上次不太一样,这是因为Father和Son中分别有一个静态成员变量,并且给静态成员变量赋值的时候还调用了各自的构造方法。

我们可以得到如下结论:
1.代码块一定会在构造方法之前加载。
2.静态代码块只会加载一次,同理静态成员变量也是。
如果把上面的两个成员变量都改成非静态的,编译器就会抛出内存溢出异常。因为非静态成员变量是属于对象的,每次创建新的对象时,都会初始化对应的非静态成员变量。然后就出现了构造方法的递归调用。

总结

1.初始化一个类时,如果它有父类,会先去初始化父类。
2.先加载静态的,再加载非静态的。
3.静态的成员变量或者静态的方法块随着类的加载而加载,如果类加载过了,就不用加载了。
4.普通代码块和构造方法都是随着对象的创建而加载,每新创建一个对象,都会加载一次。而且普通代码块在构造方法之前加载。

特殊情况

Demo3

父类:

public class Father {    public static String value      = "123";    static {        System.out.println("这是 Father 的静态代码块");    }    {        System.out.println("这是 Father 的代码块");    }    public Father() {        System.out.println("这是 Father 的构造方法");    }}

子类:

public class Son extends Father{    public Son(){        System.out.println("这是 Son 的构造方法");    }    static {        System.out.println("这是 Son 的静态代码块");    }    {        System.out.println("这是 Son 的代码块");    }}

调用代码:

    public static void main(String[] args) {        System.out.println(Son.value);    }

运行结果如下:

这是 Father 的静态代码块123

得出结论:通过子类引用父类的静态字段,不会导致子类初始化

Demo4

父类

public class Father {    static {        System.out.println("这是 Father 的静态代码块");    }    {        System.out.println("这是 Father 的代码块");    }    public Father() {        System.out.println("这是 Father 的构造方法");    }}

子类:

public class Son extends Father{    public Son(){        System.out.println("这是 Son 的构造方法");    }    static {        System.out.println("这是 Son 的静态代码块");    }    {        System.out.println("这是 Son 的代码块");    }}

调用代码:

    public static void main(String[] args) {        SuperClass[] sca = new SuperClass[10];    }

运行结果为空。

得出结论:通过数组定义来引用类,不会触发此类的初始化。

Demo5

Son类:

public class Son {    public static final String HELLOWORLD = "hello world";    public Son() {        System.out.println("这是 Son 的构造方法");    }    static {        System.out.println("这是 Son 的静态代码块");    }    {        System.out.println("这是 Son 的代码块");    }}

调用代码:

    public static void main(String[] args) {        System.out.println(Son.HELLOWORLD);    }

运行结果如下:

    hello world

得出结论:常量在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。

参考文:Java类初始化顺序

原创粉丝点击