上接深入java 虚拟机 —— 深入java虚拟机(二)——类加载器详解(上) ,在上一篇文章中,我们讲解了类的生命周期的加载和连接,这一篇我们接着上面往下看。
类的初始化 : 在类的生命周期执行完加载和连接之后就开始了类的初始化。在类的初始化阶段, java 虚拟机执行类的初始化语句,为类的静态变量赋值,在程序中,类的初始化有两种途径:( 1 )在变量的声明处赋值。( 2 )在静态代码块处赋值,比如下面的代码, a 就是第一种初始化, b 就是第二种初始化
public class Test { public static int a = 0; public static int b ; static{ b=2; } }
静态变量的声明和静态代码块的初始化都可以看做静态变量的初始化,类的静态变量的初始化是有顺序的。顺序为类文件从上到下进行初始化,想到这,想起来 一个很无耻的面试题 ,分享给大家看一下:
package com.bzu.csh; class Singleton { private static Singleton singleton = new Singleton(); public static int counter1; public static int counter2 = 0; private Singleton() { counter1++; counter2++; } public static Singleton getInstance() { return singleton; } } public class Test { public static void main(String[] args) { Singleton singleton = Singleton.getInstance(); System.out.println("counter1 = " + singleton.counter1); System.out.println("counter2 = " + singleton.counter2); } }
大家先看看这里的程序会输出什么?
不知道大家的答案是什么,如果不介意的话可以把你的答案写到评论上,看看有多少人的答案和你一样的。我先说说我刚开始的答案吧。我认为会输出:
counter1=1
Counter2= 1
不知道大家的答案是不是这个,反正我的是。下面我们来看一下正确答案:
不知道你做对没有,反正我刚开始做错了。好,现在我来解释一下为什么会是这个答案。在给出解释之前,我们先来看一个概念:
Java程序对类的使用方式可分为两种
主动使用
被动使用
•所有的Java虚拟机实现必须在每个类或接口被Java程序“ 首次主动使用 ” 时才初始化他们
主动使用(六种)
–创建类的实例
–访问某个类或接口的静态变量,或者对该静态变量赋值
–调用类的静态方法
–反射(如Class.forName(“com.bzu.csh.Test”))
–初始化一个类的子类
–Java虚拟机启动时被标明为启动类的类(JavaTest)
OK,我们开始解释一下上面的答案,程序开始运行,首先执行 main 方法,执行 main 方法第一条语句,调用 Singleton类的静态方法,这里调用Singleton类的静态方法就是主动使用Singleton类。所以开始加载Singleton类。在加载Singleton类的过程中,首先对静态变量赋值为默认值,
Singleton=null
counter1=0
Counter2=0
给他们赋值完默认值值之后,要进行的就是对静态变量初始化,对声明时已经赋值的变量进行初始化。我们上面提到过,初始化是从类文件从上到下赋值的。所以首先给Singleton 赋值,给它赋值,就要执行它的构造方法,然后执行 counter1++;counter2++; 所以这里的 counter1=1;counter2=1; 执行完这个初始化之后,然后执行 counter2 的初始化,我们声明的时候给他初始化为 0 了,所以 counter2 的值又变为了 0. 初始化完之后执行输出。所以这是的
counter1=1
counter2=0
类初始化步骤
(1)假如一个类还没有被加载或者连接,那就先加载和连接这个类
(2)假如类存在直接的父类,并且这个父类还没有被初始化,那就先初始化直接的父类
(3)假如类中存在初始化语句,那就直接按顺序执行这些初始化语句
在上边我们我们说了java 虚拟机实现必须在每个类或接口被Java程序“首次主动使用 ” 时才初始化他们,上面也举出了六种主动使用的说明。除了上述六种情形,其他使用Java类的方式都被看作是被动使用,不会导致类的初始化。程序中对子类的“主动使用”会导致父类被初始化;但对父类的“主动”使用并不会导致子类初始化(不可能说生成一个Object类的对象就导致系统中所有的子类都会被初始化)
注: 调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化。
当java虚拟机初始化一个类时,要求它的所有的父类都已经被初始化,但这条规则并不适用于接口。
在初始化一个类时,并不会先初始化它所实现的接口
在初始化一个接口时,并不会先初始化它的父接口
因此,一个父接口并不会因为它的子接口或者实现类的初始化而初始化。只有当程序首次使用特定接口的静态变量时,才会导致该接口的初始化。只有当程序访问的静态变量或静态方法确实在当前类或当前接口中定义时,才可以认为是对类或接口的主动使用。如果是调用的子类的父类属性,那么子类不会被初始化。