-
类的生命周期是:加载->验证->准备->解析->初始化->使用->卸载,只有在__准备__阶段和__初始化__阶段才会涉及类变量(static)的初始化和赋值,因此只针对这两个阶段进行分析
-
类的准备阶段需要做的是__为类变量分配内存并设置默认值__,因此类变量st为null、b为0
-
如果类变量被final修饰,编译时javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将变量设置为指定的值__,如果这里这么定义:static final int b=112,那么在准备阶段b的值就是112,而不再是0了。
-
类的初始化阶段需要做的是__执行类构造器__(类构造器是编译器__收集所有静态语句块和类变量的赋值语句__按语句__在源码中的顺序__合并生成类构造器,),因此__先执行第一条静态变量的赋值语句即st = new StaticTest (),此时会进行对象的初始化__,对象的初始化是__先初始化成员变量再执行构造方法__,因此设置a为110->打印2->执行构造方法(打印3,此时a已经赋值为110,但是b只是设置了默认值0,并未完成赋值动作),等对象的初始化完成后继续执行之前的类构造器的语句,接下来就不详细说了,按照语句在源码中的顺序执行即可。
public class StaticTest {
public static void main(String[] args) {
staticFunction();
}
static StaticTest st = new StaticTest();
static {
System.out.println("1");
}
{
System.out.println("2");
}
StaticTest() {
System.out.println("3");
System.out.println("a=" + a + ",b=" + b);
}
public static void staticFunction() {
System.out.println("4");
}
int a = 110;
static int b = 112;
static final int c = 200;
}
1° 现在, JVM要执行StaticTest类的main方法, 所以要试着加载StaticTest类;
2° 加载StaticTest类之前, 要看看StaticTest是否继承于哪个类, 如果继承的类还没有被加载, 那就先加载父类(这是一个递归加载的过程)。现在StaticTest没继承哪个类,所以加载它自己就行了(其实是先加载了Object类)
3° 准备阶段,为非final变量分配内存并设置默认初始值.此时st为null,b为0,而c由于被final修饰,所以c为200;
4° 接下来初始化阶段,此时执行类构造器将类变量赋值,执行顺序按照源码中出现的顺序(无所谓是块还是语句,一切按照源码顺序),所以先初始化构造st对象;
5° 本例的特殊之处在于st对象属于StaticTest类,因此将对象实例的初始化过程嵌入了类的静态初始化过程。实例初始化构造的顺序是先按源码顺序初始化成员变量和非静态块,再执行构造函数,因此先打印“2”(此时a还是0),再a=110,再执行st对象的构造函数,此时b仍为0;
注: 如果在非静态块中System.out.println(a)怎样办呢?
答: 会报错,不让这么用
6° 刚才第2步只是完成了 st对象的初始化过程,接下来按照顺序继续将静态块执行,所以打印“1”,b赋值为112;
4° 初始化过程结束后,调用staticFunction函数,打印“4”
因此答案为
2
3
a=110,b=0
1
4
-
内部类不会在类加载的过程中加载,除非真的要用它的静态变量或实例化一个对象(啥时候真正用到它啥时候加载)
所以,这就诞生了另一种使用静态内部类写单例模式的写法
public class Singleton { private Singleton() {} private static class SingletonHolder { private static Singleton instance = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.instance; } }
解释
因为内部静态类是要在有引用了以后才会装载到内存的,所以在第一次调用getInstance()之前,SingletonHolder是没有被装载进来的,只有在第一次调用了getInstance()之后,里面涉及到了return SingletonHolder.instance,产生了对SingletonHolder的引用,内部静态类的实例才会真正装载;
又,虚拟机会保证一个类的()方法在多线程环境下可以被正确的加锁、同步,因此多线程同时初始化一个类的时候,只有一个线程会执行这个类的()方法。同一个类加载器下,一个类型只会初始化一次