总结:类加载、初始化实现顺序

类的加载着重介绍一下一下几个步骤。


虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类)

 加载:加载主类


准备:为类变量分配内存并设置类变量初始值(0或NULL),如果有static final 变量(对象),准备阶段赋值 static变量(对象)。
类变量:static修饰的变量:O或NULL
static final变量:因为在定义的时候必须赋初始值,在准备阶段直接赋值。
实例变量:在对象实例化时随对象一起分配在堆中。

 

初始化:按顺序初始化变量和其他资源。


new关键字实例化对象;

读取或设置一个类变量(static修饰),调用类的静态方法。如果有static对象、static变量、static代码块 初始化;

如果有父类还没有初始化,需要先触发父类的初始化。

对类进行反射调用。

等等。

例如:

 

package test;

public class Test {
	Father father2 = new Father("普通成员Father2 构造函数");
	static {
		System.out.println("静态块执行");
	}
	{
		System.out.println("普通代码块执行");
	}
	static Father father1 = new Father("静态成员Father1 构造函数");
	
	Father father3 = new Father("普通成员Father3 构造函数");

	public Test(){
		System.out.println("Test类默认构造函数调用");
	}
	public static void main(String[] args) {
		System.out.println("第一个主类对象");
		Test test1 = new Test();
		
		System.out.println("第二个主类对象");
		Test test2 = new Test();
	}
}

class Father {
	public Father(String s) {
		System.out.println(s);
	}

	{
		System.out.println("******father init");
	}
	static {
		System.out.println("*******father static init");
	}
}

 

 执行结果:

静态块执行
*******father static init
******father init
静态成员Father1 构造函数
第一个主类对象
******father init
普通成员Father2 构造函数
普通代码块执行
******father init
普通成员Father3 构造函数
Test类默认构造函数调用
第二个主类对象
******father init
普通成员Father2 构造函数
普通代码块执行
******father init
普通成员Father3 构造函数
Test类默认构造函数调用

 

分析:

 1、执行main()方法的时候先加载主类Test并初始化。

初始化的时候,对主类的静态对象、静态变量、静态代码块初始化(按顺序)。所以Test类的静态代码块、静态对象father1被初始化。(输出1-4行)

其中,静态对象father1初始化的时候,需要对类Father加载并初始化。同样,Father类初始化的时候,对静态代码块初始化。在类的加载过程中,只有内部的变量创建完,才会去执行这个类的构造方法。所有的变量初始化完,才会执行构造方法。

最后调用构造函数。

2、Test test1 = new Test();(输出6-11行)

初始化Test类,静态成员和静态代码块只进行了一次初始化。

所以只初始化Test类的普通对象和普通代码块。

3、Test test2 = new Test();(输出13-18行)

注意:

1、类的静态成员和静态代码块在类加载中是最先进行初始化的,并且只进行一次。

2、执行顺序为:

  1. static静态代码块和静态成员
  2. 普通成员
  3. 构造函数执行

3、类加载检查:

JVM遇到一条new指令时,首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类的加载过程。

 

猜你喜欢

转载自420532394.iteye.com/blog/2368237