Java 类的加载顺序

今天来聊一聊Java类的加载顺序,话不多说,直接上干货。

public class Father {
    public static Print STATIC_COLUMN = new Print("父类静态字段1");
    public Print dynamicColumn = new Print("父类成员变量1");
    static{
        new Print("父类静态代码块");
    }
    {
        new Print("父类非静态代码块");
    }
    public Father(){
        new Print("父类无参数构造器");
    }
    public Father(Print print){
        new Print("父类有参构造器");
    }
    static class StaticInnerClz{
        static Print STATIC_COLUMN = new Print("父类静态内部类静态成员");
        public StaticInnerClz(){
            new Print("父类静态内部类构造器");
        }
    }
    public static Print STATIC_COLUMN2 = new Print("父类静态字段2");
    public Print dynamicColumn2 = new Print("父类成员变量2");
}

public class Children extends Father{
    public static Print STATIC_COLUMN = new Print("子类静态字段1");
    public Print dynamicColumn = new Print("子类成员变量1");
    static{
        new Print("子类静态代码块");
    }
    {
        new Print("子类非静态代码块");
    }
    public Children(){
        new Print("子类无参数构造器");
    }
    public Children(Print print){
        new Print("子类有参构造器");
    }
    static class StaticInnerClz{
        static Print STATIC_COLUMN = new Print("子类静态内部类静态成员");
        public StaticInnerClz(){
            new Print("子类静态内部类构造器");
        }
    }
    public static Print STATIC_COLUMN2 = new Print("子类静态字段2");
    public Print dynamicColumn2 = new Print("子类成员变量2");
}

父子类的代码除了Print类的入参字符串,其他是一模一样的。

Print类是我写的一个方便打印的辅助类

public class Print {
    public String msg;
    public Print(String msg){
        this.msg = msg;
        System.out.println(msg);
    }
}
    public static void main(String[] args) {
        Children f = new Children(new Print("构造器入参"));
    }

 直接运行查看打印结果:

父类静态字段1
父类静态代码块
父类静态字段2
子类静态字段1
子类静态代码块
子类静态字段2
构造器入参
父类成员变量1
父类非静态代码块
父类成员变量2
父类无参数构造器
子类成员变量1
子类非静态代码块
子类成员变量2
子类有参构造器

分析结果我们可以发现:

  • 以"构造器入参"为分水岭,前半部分是静态资源加载,后半部分是动态资源加载。
  • 然后各自又存在父类优先于子类的加载顺序。
  • 构造器在最后才执行。
  • 从静态字段和静态代码块的加载顺序来看,是按照代码顺序来执行,并不存在优先级的差别。
  • 静态内部类在外部类初始化的过程中并不会同时初始化。

因此我们可以分析总结出如下规则:

  1. 静态优先于动态
  2. 父类优先于子类
  3. 字段优先于构造器

多条件情况下,按照1 > 2 > 3的规则执行。

接下来再做一下扩展,main方法增加一些代码:

    public static void main(String[] args) {
        Children f = new Children(new Print("构造器入参"));
        System.out.println("=====初始化结束======");
        Children f2 = new Children(new Print("再次初始化,构造器入参"));
        System.out.println("=====初始化结束======");
        Children.StaticInnerClz innerClz = new Children.StaticInnerClz();
    }

运行查看结果:

父类静态字段1
父类静态代码块
父类静态字段2
子类静态字段1
子类静态代码块
子类静态字段2
构造器入参
父类成员变量1
父类非静态代码块
父类成员变量2
父类无参数构造器
子类成员变量1
子类非静态代码块
子类成员变量2
子类有参构造器
=====初始化结束======
再次初始化,构造器入参
父类成员变量1
父类非静态代码块
父类成员变量2
父类无参数构造器
子类成员变量1
子类非静态代码块
子类成员变量2
子类有参构造器
=====初始化结束======
子类静态内部类静态成员
子类静态内部类构造器

分析结果我们可以发现:

  • 再次初始化一个新的子类对象时,不再加载静态成员,静态代码块。
  • 静态内部类和外部类一样需要程序员触发调用才开始初始化。(启动类由JVM触发加载)

版权声明:本文为qq_36389344原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。