1.综述
主要分析以下几点:
1.为什么父类静态代码块优先于子类静态代码块执行?
2.为什么静态代码块只执行一次?
2.概念
类加载的时机
类加载的全过程是加载、验证、准备、解析和初始化这5个阶段。
那么什么时候需要开始类加载的初始化阶段?这个java虚拟机规范有严格的规定,有且仅有(这句话是不是很熟悉)五种情况必须立即进行“初始化”,这里主要介绍两条:
(1)当虚拟机启动时,用户需要指定一个要执行的主类(即包含main()方法的那个类),虚拟机会先“初始化”这个主类。
(2)当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
初始化
“初始化”是类加载过程的最后一步,它本身是执行类构造器clinit()方法的过程。
关于clinit()方法有以下几点:
(1)clinit()方法是由编译器自动收集类中的所有类变量(被static修饰的变量)和静态块(static{})合并产生的;
(2)虚拟机会保证在子类的clinit()方法执行之前,父类的clinit()方法已经执行完毕;
对象的创建
当虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被“加载”、“解析”和“初始化”过。如果没有,那么必须先执行相应的类加载过程。
3.例一
先上代码
class Fu {
static {
System.out.println("fu的静态代码块");
}
}
public class Zi extends Fu {
static {
System.out.println("zi的静态代码块");
}
public static void main(String[] args) {
System.out.println("第一次:");
new Zi();
System.out.println("第二次:");
new Zi();
}
}
运行结果是:
fu的静态代码块
zi的静态代码块
第一次:
第二次:
产生这个结果的过程分析如下:
(1)根据上面提到的java虚拟机必须立即“初始化”的第一条。java虚拟机会先“初始化”Zi这个主类;
(2)但是会发现Zi类所继承的Fu这个类还没有进行过初始化(第二条),则会先触发Fu类的“初始化”;
(3)“初始化”的过程其实就是执行clinti()方法的过程,而clinit()方法又是由类中的所有类变量(被static修饰的变量)和静态块(static{})合并产生的,那么System.out.println(“fu的静态代码块”);这条语句就执行了;
(4)Fu类“初始化”结束后开始Zi类的“初始化”,执行语句System.out.println(“zi的静态代码块”);
(5)之后再执行主类的main()方法,当new Zi()的时候,发现Zi类已经加载过,便不再进行类加载,那么就没有“初始化阶段”,没有clinit()方法的执行,没有static{}的执行。
4.例二
代码
class Father {
static {
System.out.println("father的静态代码块");
}
}
class Son extends Father {
static {
System.out.println("son的静态代码块");
}
}
public class Demo {
public static void main(String[] args) {
System.out.println("第一次:");
new Son();
System.out.println("第二次:");
new Son();
}
}
运行结果
第一次:
father的静态代码块
son的静态代码块
第二次:
结果分析如下:
(1)同理,先加载主类Demo类,它没有i继承父类,也没有类变量(static修饰的变量)和静态块(static{})。(多说一句因为所有类都是java.lang.Object的子类,因此虚拟机中第一个被执行的clinit()方法的类肯定是java.lang.Object类);
(2)接着执行主类的main()方法,先执行语句System.out.println(“第一次:”); 在new Son()时,发现Son类还没有执行类加载过程;
(3)想对Son类进行类加载,又发现Son类继承了Father类,那么先对Father类进行类加载过程,上面介绍到的类加载的时机的第二条和初始化的第二条都会保证这一点;
(4)之后是System.out.println(“第二次:”);这条语句执行;
(5)最后第二次new Son()的时候,这时Son类已经类加载过,不再执行类加载过程。