天天看点

java 中的静态变量,静态代码块,动态代码块,构造方法执行顺序的深入探究

要想完全弄懂这个执行顺序,需要我们先了解几个概念。

首先是类加载与对象的构造,类加载就是在第一次调用这个类的时候jvm虚拟机会通过类加载器在一个叫做方法区的逻辑内存中将所要用到的类的信息存放在里边,其中方法区有一个静态区,存放的是类中的静态(类变量),而对象构造则只是在堆中开辟一个内存空间将实例化的对象存放在里边,在生命周期中要远远小于类。

第二是java中关于静态代码块与动态代码块的注释,静态代码块只有在类加载时仅且执行一次,而动态代码块是在每次构造对象时执行一次,然后才是构造方法,也就有了一个顺序,静态代码块>动态代码块>构造方法,而静态变量与静态代码块的优先级是一个级别的,这样看来好像问题解决了,但其实问题并不是这么简单。

接下来还是找一个有说服力的例子,阿里巴巴的一道面试题。

//package com.basics.day09;

public class Test {
    public static int k = 0;
    public static Test t1 = new Test("t1");
    public static Test t2 = new Test("t2");
    public static int i = print("i");
    public static int n = 99;
    private int a = 0;
    public int j = print("j");
    
    {
        print("构造块");
    }
 
    static {
        print("静态块");
    }
 
    public Test(String str) {
        System.out.println((++k) + ":" + str + "    i=" + i + "     n=" + n);
        ++i;
        ++n;
    }
 
    public static int print(String str) {
        System.out.println((++k) + ":" + str + "    i=" + i + "     n=" + n);
        ++n;
        return ++i;
    }
 
    public static void main(String args[]) {
        Test t = new Test("init");
    }

}
           

运行结果:

1:j    i=0     n=0
2:构造块    i=1     n=1
3:t1    i=2     n=2
4:j    i=3     n=3
5:构造块    i=4     n=4
6:t2    i=5     n=5
7:i    i=6     n=6
8:静态块    i=7     n=99
9:j    i=8     n=100
10:构造块    i=9     n=101
11:init    i=10     n=102
           

这样的结果是不是与我们所想的不一样,按照上边的那个顺序,应该是先静态变量,然后是静态代码块,然后是动态代码块,最后才是构造函数,可是上边的结果,很明显是先执行的非静态的赋值变量,然后动态代码块,然后构造方法,静态代码块一次都没有执行,一直等到第八次才执行出来。

那么问题就来了,java中不是说静态代码块是在类加载时便执行一次,可是这里为什么不执行。

解答这个问题还得在看一段代码:

package com.basics.day10;

public class TestStatic {
    
    
    static {
        System.out.println("这时TestStatic的静态代码块!");
    }
    
    {
        System.out.println("这是动态代码块!");
    }
    public TestStatic() {
        System.out.println("这是构造代码块");
    }
    
    public static void main(String[] args) {
        TestStatic te = new TestStatic();
        TestStatic t2 = new TestStatic();
    }
    
}
// 请注意看这个类中的静态代码块
class Annoy {
    static {
        System.out.println("Annoy的静态代码块");
    }
}
           

执行结果:

这时TestStatic的静态代码块!
这是动态代码块!
这是构造代码块
这是动态代码块!
这是构造代码块
           

这个结果是不是就与我们所理解的一样,但是重点不是在这里,重点是我们声明的第二类Annoy,里边的静态代码块没有被执行,这是为什么呢???

呵呵,这还是类加载的问题,因为我们在主类中并没有引用这个类,也就是这个类并没有被加载进方法区,既然它没有被加载,那么其中的代码块自然就不会执行了。

这里有一个很重要的概念,就是类的加载只有在引用它的时候才会加载,而不是直接加载进方法区的,这也是类加载与构造对象的区别。构造对象并不是类加载,它们是两个完全不一样的概念,类加载要在对象构造之前。

到了这里,我们折回到阿里巴巴的那道题中,发现他在第二行的时候进行了对象的构造,而在这个构造中执行的都是非静态的代码块。我是这样理解的,在这个类的main函数被jvm识别的时候,这个类已经加载到方法区中去了,这时候静态都会被执行一次,而且是按照声明的顺序执行的,正常下来,先k,t1,t2...跳过j,到静态代码块,但这里t1有了一个变故,那就是他构造了一个对象,这时类已经加载完成,相当于静态已经默认被执行了一次,只是顺序上还没有轮到,根据静态的特性,他只执行一次,所以t1这里的分支,调用的就是动态代码块以及构造方法,不会去管那些静态,这也就导致出现了上边的那种情况。

到此,这个问题就解决完成了。

继续阅读