今天遇到一个很诡异的bug,调试了半天也没有看出来有什么问题,抽象一下代码如下:
public class Instance
{
public static Instanceinstance= new Instance();
public static Mapmap= new HashMap();
public static Instance instance()
{
return Instance.instance;
}
public static void main(String[] args)
{
Instance.instance().test();
}
public Instance()
{
Instance.map.put("abc", "def");
}
public void test()
{
System.out.println("test called");
}
}
结果一运行就出下面的错误,
java.lang.ExceptionInInitializerError
Caused by: java.lang.NullPointerException
at edu.bupt.test.Instance.(Instance.java:27)
at edu.bupt.test.Instance.(Instance.java:9)
Exception in thread "main"
当时没有太注意这个Exception,因为还有另外一个NullException
后来仔细看了一下堆栈才发现,这是在初始化的过程中出的问题,
首先我们来回忆一下类的加载过程
1、加载类文件
2、初载父类
3、初始化静态变量和static代码块
4、调用构造函数
好了,问题就出在第3步了,因为这里我们用了一个非标准的单态模式,
直接把静态变量在static里初始化了。由于第三步的初始化步骤是根据你
的变量的定义顺序来初始化的,于是杯具产生了:
首先,你要初始化static Instance instance,这样你就要调用Instance
的构造函数,这个时候,构造函数里要访问map变量,而这个static变量
还没有初始化呢,于是空指针就出现了。
结论:要仔细阅读栈的话,这个问题还是很容易定位的,这么看init代表构造函数
而cinit代表静态初始化函数,因此这个过程其实是在静态初始化调用构造函数时
发生的,去看一个构造函数分析一下就得到结果了。
另外尽可能避免地使用在变量定义时就初始化变量,这样会导致一些问题的追查
比较麻烦,比如这个问题就是一个很典型的例子,解决问题的办法居然是要将两个
变量定义顺序调整一下,如果使用了一些代码format工具,比如eclipse的format功能,
这种诡异的bug就会出现了。