首先我们先看一个示例程序:
package com.tfdd.test; /** * @desc 类加载校验 * @author chenqm * @date 2016年2月2日 */ class Singleton{ private static Singleton singleton = new Singleton(); public static int count1 ; public static int count2 = 0; private Singleton(){ count1++; count2++; } public static Singleton getInstance(){ return singleton; } } public class SingletonTest { public static void main(String[] args) { Singleton singleton = Singleton.getInstance(); System.out.println(singleton.count1); System.out.println(singleton.count2); } }
猜猜输出的结果是什么?据说80%的java程序猿都会犯的错误!
1
0
就是这样一个结果,我们先不说为什么。接着讲我们的类加载器.
类的加载大致分为三个部分:加载,连接,初始化。
加载:查找并加载类的二进制数据
连接:1.验证(确保被加载类的准确性) 2.准备(为类的静态变量分配内存,并将其初始化为默认值) 3.解析(将类中的符号引用转化为直接引用)
初始化:为类的静态变量赋予正确的初始值(即赋上我们给出的值)
然后我们再看看java程序对类的使用方式:主动使用和被动使用。请注意下面我说的这句话
所有的JAVA虚拟机实现必须在每个类或接口被JAVA程序“首次主动使用”时才初始化他们。
那么什么叫主动使用?基本上分为6种情况:
--创建类的实例
--访问某个类或接口的静态变量,或者对该静态变量赋值
--调用类的静态方法
--反射
--初始化一个类的子类
--java虚拟机启动时被标明为启动类的类
了解了这些知识之后,我现在来回答之前的问题,
Singleton.getInstance(); 调用了类的静态方法,符合首次主动使用该类的情况!那么我们进入初始化阶段。
初始化已经属于类加载的第三步了,在第二步的连接的准备部分,已经赋过一次默认值了。所以应该是这样一个过程:
1.singleton = null;count1=0;count2=0
2.singleton = new Singleton(); 此时执行构造函数,结果为count1=1;count2=1
3.count1没有被我们赋值跳过初始化,count2赋值为0所以结果为count1=1;count2=0
(*注意) 类的静态变量赋值的顺序是按照代码的书写的顺序执行的。
修改代码如下:
package com.tfdd.test; /** * @desc 类加载校验 * @author chenqm * @date 2016年2月2日 */ class Singleton{ public static int count1 ; public static int count2 = 0; private static Singleton singleton = new Singleton(); private Singleton(){ count1++; count2++; } public static Singleton getInstance(){ return singleton; } } public class SingletonTest { public static void main(String[] args) { Singleton singleton = Singleton.getInstance(); System.out.println(singleton.count1); System.out.println(singleton.count2); } }
输出结果为
1
1
证明赋值的顺序是根据代码书写的先后顺序执行的!