<clinit>()<init>()

<clinit>()

  • <clinit>()的执行时期为类初始化阶段,并且该方法只能被jvm使用,专门负责类变量的初始化工作。
  • Java 类加载的初始化过程中,编译器按语句在源文件中出现的顺序,依次自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并产生 <clinit>()方法。 如果类中没有静态语句和静态代码块,那可以不生成该方法。
  • <clinit>() 不需要显式调用父类(接口除外,接口不需要调用父接口的初始化方法,只有使用到父接口中的静态变量时才需要调用)的初始化方法 <clinit>(),虚拟机会保证在子类的 <clinit>() 方法执行之前,父类的 <clinit>() 方法已经执行完毕。

类何时进行初始化?(主动引用)

  1. 使用new关键字实例化对象的时候;读取或设置一个类型的静态字段(被final修饰的除外,因为已在编译期将结果放入了常量池);调用一个静态方法。
  2. 使用反射调用。
  3. 初始化类的时候,发现其父类还没有进行初始化,则需要先触发其父类的初始化。
  4. 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
  5. 当使用JDK7新加入的动态语言支持时。(后续在字节码章节详细讲解)
  6. 当一个接口中定义了JDK8新加入的默认方法(被default关键字修饰的接口方法)时,如果这个接口的实现类发生了初始化,那么该接口要在其之前被初始化。

以上的条件是当类或接口未初始化过,即是第一次初始化,才会执行。并且有且只有这六种场景中的行为称为对一个类型进行主动引用。除此之外,所有引用类型都不会触发初始化,称为被动引用

被动引用

  1. 通过子类引用父类的静态字段,不会导致子类初始化,只会触发父类的初始化。

  2. 通过数组定义来引用类。

假如有一个Student类

Student[] studentArray = new Student[10];

这种情况并不会导致Student类进行初始化。你并没有使用Student类中的任何,但是你声明数组,必然会导致和数组相关的类进行了初始化,[Lorg.fenixsoft.classloading.SuperClass 这个类发生了初始化,这是个与一维数组相关的类,具体不做详细介绍。

  1. 使用类中的被final修饰的静态常量。

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

<init>()

  • 执行时期:对象的初始化阶段。

  • 执行构造器,以及非静态初始化块中的代码。

实例化一个类的四种途径:

  1. 调用 new 操作符
  2. 调用 Class 或 java.lang.reflect.Constructor 对象的newInstance()方法(反射)
  3. 调用任何现有对象的clone()方法
  4. 通过 java.io.ObjectInputStream 类的 getObject() 方法反序列化

<clinit>()<init>()执行顺序案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Test {
private static Test instance; // <clinit>

static {
// <clinit>()
System.out.println("static开始");
instance = new Test();
System.out.println("static结束");
}

public Test() {
// <init>()
System.out.println("构造器开始");
System.out.println("x=" + x + ";y=" + y);
x++;
y++;
System.out.println("x=" + x + ";y=" + y);
System.out.println("构造器结束");
}

public static int x = 1; // <clinit>
public static int y; // <clinit>

public static Test getInstance() {
return instance;
}

public static void main(String[] args) {
Test.getInstance();
System.out.println("x=" + x);
System.out.println("y=" + y);
}
}

输出结果为

static开始
构造器开始
x=0;y=0
x=1;y=1
构造器结束
static结束
x=1
y=1

从结果中可以看出静态代码块先执行,如果静态代码块中执行了其它方法,则先去执行,然后在回来继续执行,静态代码,这就说明了<clinit>()方法在<init>()之前执行。

<clinit>()方法就是将一个类中的所有的静态代码块以及类变量整合到一块,注意这里是有顺序的执行,如果其中调用了其它非静态方法,就会执行完后接着执行,如果有父类的话当然执行父类的静态代码块以及类变量,就像下图一样,把父类和子类的这些静态代码块,类变量全部整合到一块,按照代码编写的顺序来执行。

<init>()也是如此,不过整合的是普通代码块和普通的成员变量了。