代码如下:
public class Main {
public static void main(String[] args) throws Exception {
Foo foo = Foo.getInstance();
}
}
class Foo {
private static Foo instance = new Foo();
private static List<String> list = new ArrayList<>();
public static Foo getInstance() {
return instance;
}
public Foo() {
list.add("Hello");
}
}
运行时异常如下:
Exception in thread "main" java.lang.ExceptionInInitializerError
at com.company.Main.main(Main.java:11)
Caused by: java.lang.NullPointerException
at com.company.Foo.<init>(Main.java:26)
at com.company.Foo.<clinit>(Main.java:18)
... 1 more
通过异常可知是这行代码出了空指针异常:list.add("Hello");
,很明显,list
对象为空,我这个代码已经是简化之后的,在真实项目中代码很多的时候是有点懵人,list
是一个静态变量,而Foo()
函数是一个实例函数,一般理解就是静态变量先初始化的啊,为什么会是空啊!没错,静态变量先初始化,但是这里Foo
实例也是在静态变量中直接初始化的:private static Foo instance = new Foo();
,所以这行代码先执行了,先要初始化instance
这个静态变量,而你在初始化这个变量的时候又用到了另一个静态变量list
,而这个静态变量声明是在后面的,所以还没初始化到,所以就为null了,解决方案有两种:
方式一:把list
声明在instance
的前面,如下:
class Foo {
public static List<String> list = new ArrayList<>();
private static Foo instance = new Foo();
}
这样我们在调用 Foo.getInstance()
的时候,它就加载了Foo的Class到内存中,一加载Class就会初始化静态变量,多个静态变量是按声明的先后顺序声明的,所以list
先声明就会先初始化,然后在初始化instance
,此时的list
就不为空了。
方式二:不修改list
的声明位置也没关系,但是需要把list
的static
修饰符去除掉,如下:
class Foo {
private static Foo instance = new Foo();
public List<String> list = new ArrayList<>();
}
Foo
的字节码一加载进内存就会初始化静态变量instance
,而instance
是new
了一个Foo()
对象,所以此时会初始化Foo
上面的成员变量list
(非静态成员变量),所有的成员变量都初始化完成之后,再会执行Foo()
构造函数中的代码,所以此时list
也不会为空了。