java中静态语句块、实例代码块、构造器方法这3者的调用顺序

时间:2022-11-02 19:32:45

1、分析:

1.1、在JVM类加载机制中,有讲到:将类加载到JVM当中后,才进行类的初始化。所谓初始化阶段,是指:根据程序员写的代码去初始化类变量和其他资源,这句话也可以这么说:初始化阶段是执行类构造器<clinit>()方法的过程。<clinit>()方法是编译器自动收集类中的所有类变量和静态语句块(static{})中的语句合并而成的。知道这一点很重要,而<clinit>()方法里面语句的顺序由源程序代码决定。<clinit>()方法和类实例构造器<init>()方法是不同的。这一点,可以通过调试代码来验证,我用的是Mac版本的idea15,在屏幕的最下面一行,可以看到先执行<clinit>()方法,后执行<init>()方法。如图:

java中静态语句块、实例代码块、构造器方法这3者的调用顺序



java中静态语句块、实例代码块、构造器方法这3者的调用顺序

1.2、调用完<clinit>()方法后,才会执行类的构造函数<init>()方法。涉及到构造方法的调用、实例代码块的执行。同时,实例化几次类,则进行“实例代码块”和“构造器方法”的几次调用,并且,“实例代码块”优先于“构造器方法”的调用。



2、验证代码:

/**
* Created by cxh on 17/07/21.
*/

public class Main {


//实例化代码块.每次生成类实例,都会执行.并且,实例化代码块的执行 优先于 构造器.
{
System.out.println("blockA");
}

//静态语句块,在类初始化时,仅仅执行一次.
static{
System.out.println("blockB");
}

//类实例
public static Main t1 = new Main();

//构造器方法
Main(){
System.out.println("constructor");
}


public static void main(String[] args)
{
//类实例
Main t2 = new Main();
}
}

输出结果:

blockB
blockA
constructor
blockA
constructor

Process finished with exit code 0


3、说明:

3.1、名字上的区分

<clinit>()方法的名字:类构造器方法

<init>()方法的名字:  实例构造器方法  or  类的构造函数

3.2、说一下<clinit>()方法

3.2.1、<clinit>()方法中的内容由编译器自动收集类中的2类东西组成:类变量和静态语句块中的语句。在<clinit>()方法中各个语句的排列顺序和java代码顺序保持一致。这样的顺序也决定了:静态语句块中只能访问静态语句块之前的静态变量;定义在它后面的变量,是不能被访问的,但是可以为其赋值。如:

java中静态语句块、实例代码块、构造器方法这3者的调用顺序

修改后代码:

/**
* Created by cxh on 17/07/21.
*/

public class Main {


static int a=0;
static{
System.out.println("blockB");
System.out.println(a);
//System.out.println(b); //报错:Illegal forward reference
b=3;

}
static int b=1;

public static void main(String[] args)
{
//类实例
Main t2 = new Main();
System.out.println("b:"+b);
System.out.println("a:"+a);
}
}


运行结果:
blockB
0
b:1
a:0

3.2.2、<clinit>()方法与类的构造函数(or 说实例构造器方法<init>()方法)不同。它不需要显示的调用父类构造器,虚拟机会保证在子类的<clinit>()方法执行之前,父类的<clinit>()已经之行完毕。因此在虚拟机中第一个被执行的<clinit>()方法的类肯定是java.lang.Object。

3.2.3、由于父类的<clinit>()方法先执行,也就意味着父类中定义的静态语句块     先于    子类的变量赋值操作。

3.2.4、<clinit>()对于类和接口来说,并不是必需的。因为如果一个类中没有静态语句块,也没有对类变量的赋值操作,那么编译器可以不为这个类生成<clinit>()方法。

3.2.5、接口中不能使用静态语句块,但仍然有变量初始化的赋值操作。因此接口和类一样都会生成<clinit>()。只有当父接口中定义的变量使用是,父接口才会初始化。另外,接口的实现类在初始化时,也一样不会执行接口的<clinit>()方法。

3.2.6、虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。如果在一个类中的<clinit>()方法有很耗时的操作,就可能造成多个线程阻塞,在实际应用中,这种阻塞是很隐蔽的。

注:需要注意的是,其他线程虽然会被阻塞,但如果执行<clinit>()方法的那条线程退出<clinit>()方法后,其他线程不会再执行<clinit>()方法。同一个类加载器,一个类型只会初始化一次。