1、延伸阅读

类加载过程: https://www.cnblogs.com/ysocean/p/11427536.html

2、Java类加载

静态成员属性的初始化早于静态代码块;

静态代码块是指的类的初始化操作,初始化早于对象的创建;

类静态域的只会初始化一次

3、类加载分为以下步骤

整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段。

  • 加载:二进制.class文件各种文件加载一遍,类加载器去加载的
  • 验证:验证类的正确性,像是格式啊、方法重写啥的啊
  • 准备:静态域的初始化赋值,这个是给个默认值啊。
  • 解析:符号引用(编程原理)解析为直接引用。
  • 初始化:静态域的代码里面赋值。

4、初始化过程,就是常识中的静态域加载的过程

以下四种情况触发

  • 遇到new、getstatic、putstatic、invokestatic这四条字节码指令时,如果类还没有进行过初始化,则需要先触发其初始化。生成这四条指令最常见的Java代码场景是:使用new关键字实例化对象时、读取或设置一个类的静态字段(static)时(被static修饰又被final修饰的,已在编译期把结果放入常量池的静态字段除外)、以及调用一个类的静态方法时;
  • 使用Java.lang.refect包的方法对类进行反射调用时,如果类还没有进行过初始化,则需要先触发其初始化;
  • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化;
  • 当虚拟机启动时,用户需要指定一个要执行的主类,虚拟机会先执行该主类;

除了上面四种情况,有几种特殊情况。类的初始化会被动加载

  • 通过子类引用父类中的静态字段,这时对子类的引用为被动引用,因此不会初始化子类,只会初始化父类

    class Father {
      public static int m = 33;
    
      static {
          System.out.println("父类被初始化");
      }
    }
    
    class Child extends Father {
      static {
          System.out.println("子类被初始化");
      }
    }
    
    public class StsticTest {
      public static void main(String[] args) {
          // System.out.println(new Child());
          System.out.println(Child.m);
      }
    }
  • 常量在编译阶段会存入调用它的类的常量池中,本质上没有直接引用到定义该常量的类,因此不会触发定义常量的类的初始化。这里实际上完成了“准备”阶段
public class Const {
    public static final String NAME = "我是常量";
    
    static {
        System.out.println("初始化Const类");
    }
}

public class Test {
    public static void main(String[] args) {
        System.out.println(Const.NAME);
    }
}
  • 通过数组定义来引用类,不会触发类的“初始化”
public class Const { 
    static {
        System.out.println("初始化Const类");
    }
}

public class Test {
    public static void main(String[] args) {
        Const[] con = new Const[5]
    }
}

5、对象的初始化顺序

父类静态(变量、代码块,先声明先执行)--->子类静态(变量、代码块,先声明先执行)--->父类非静态--->父类构造方法--->子类非静态--->子类构造方法

注意:子类的构造方法,不管这个构造方法带不带参数,默认的它都会先去寻找父类的不带参数的构造方法。如果父类没有不带参数的构造方法,那么子类必须用supper关键子来调用父类带参数的构造方法,否则编译不能通过。

6、练习

练习1

class Student {
    static {
        System.out.println("Student静态代码块");
    }

    {
        System.out.println("Student构造代码块");
    }

    public Student() {
        System.out.println("Student构造方法");
    }
}

class StudentTest {
    static {
        System.out.println("StudentTest静态代码块");
    }

    public static void main(String[] args) {
        System.out.println("main 方法");

        Student s1 = new Student();
        Student s2 = new Student();
    }
}
StudentTest静态代码块

我是main方法

Student 静态代码块 

Student 构造代码块

Student 构造方法

Student 构造代码块

Student 构造方法

解析:

1、main方法是Java程序的入口,JVM先main方法,先把main方法的类加载到内存中,此时StudentTest类的静态代码块直接随着类的加载而执行;

2、随着main方法开始执行,当Student类被实例化后,Student类的静态代码块先执行,并且只执行一次,不管实例化多少对象;

3、每实例化一次Student类,类中的构造代码块和构造方法就会执行一次;

练习2

public class Father {
    public static int m = 33;

    static {
        System.out.println("父类被初始化");
    }
}

public class Child extends Father {
    static {
        System.out.println("子类被初始化");
    }
}

public class StsticTest {
    public static void main(String[] args) {
        System.out.println(new Child());
        System.out.println(Child.m);
    }
}

解析:

1、通过子类引用父类中的静态字段,这时对子类的引用为被动引用,因此不会初始化子类,只会初始化父类

因此第一步结果为:

父类被初始化
33

2、new Chid初始化,先父类后子类,若父类已经加载则跳过

因此第二步结果为:

子类被初始化
Test.JUnit.Child@ad8086

结果:

父类被初始化
33
子类被初始化
Test.JUnit.Child@ad8086

练习3

class Other {
    public static Other o1 = new Other();
    public static Other o2 = new Other();
    
    static {
        System.out.println("静态块");
    }

    {
        System.out.println("构造块");
    }

    public static void main(String[] args) {
        Other other = new Other();
    }
}

解析:

1、main函数中的new Other()初始化开始;

2、先加载静态变量,后静态快(类静态域只会加载一次);

3、加载静态变量;

4、加载静态快;

结果:

构造块
构造块
静态块
构造块

标签: none

添加新评论