【Java】static执行顺序详解(什么情况下父类普通代码块会先运行?)

版权声明:本文为博主原创文章,转载请注明出处:https://twocups.cn/index.php/2020/01/22/16/

关于 Java 中 static 执行顺序的问题,网上有很多解答。但随着我研究的深入,我发现 static 实际上的执行顺序比网上说的复杂得多。正好网上也没有其他这方面的资料,所以我来给大家详细解释一下 static 的具体执行顺序。

先上个最复杂的例子

//父类parent
class parent {
    private static parent a=new parent(); //父类静态变量
    static{
        System.out.println("父类静态代码块");
    }
    {
        System.out.println("父类普通代码块");
    }
    public parent(){
        System.out.println("父类构造方法");
    }
}

//子类sub
class sub extends parent{
    private static sub b = new sub(); //子类静态变量
    static{
        System.out.println("子类静态代码块");
    }
    {
        System.out.println("子类普通代码块");
    }
    public sub(){
        System.out.println("子类构造方法");
    }
    public static void main(String[] args) {
        System.out.println("main");
        new parent();
        System.out.println("parent-sub");
        new sub();
    }
}

你觉得以上例子的输出结果是怎样的呢?

实际上是这样:

父类普通代码块
父类构造方法
父类静态代码块
父类普通代码块
父类构造方法
子类普通代码块
子类构造方法
子类静态代码块
main
父类普通代码块
父类构造方法
parent-sub
父类普通代码块
父类构造方法
子类普通代码块
子类构造方法

你也许觉得这是错的!父类普通代码块怎么可能先运行呢?!要么先运行父类静态方法,要么要先运行父类静态代码块,怎么样也轮不到父类普通代码块先运行啊!

但其实这就是实际上的运行顺序,不信的话你可以自己尝试运行一下。

那为什么会这样呢?别急,慢慢看下去。

我们来个最简单的例子(情况1)

//父类parent
class parent {
    static{
        System.out.println("父类静态代码块");
    }
    {
        System.out.println("父类普通代码块");
    }
    public parent(){
        System.out.println("父类构造方法");
    }
}

//子类sub
class sub extends parent{
    static{
        System.out.println("子类静态代码块");
    }
    {
        System.out.println("子类普通代码块");
    }
    public sub(){
        System.out.println("子类构造方法");
    }
//    public static void main(String[] args) {
//        System.out.println("main");
//        new sub();
//    }

}

//主方法类Test
public class Test{
    public static void main(String[] args){
        System.out.println("main");
        new sub();
    }
}

输出结果为:

main
父类静态代码块
子类静态代码块
父类普通代码块
父类构造方法
子类普通代码块
子类构造方法

这个执行顺序应该没有人有异议吧,毕竟这是最基本的执行顺序,就不解释了,网上这种基础的讲解很多。

把 main 放在 sub 类里面里面(情况2)

//父类parent
class parent {
    static{
        System.out.println("父类静态代码块");
    }
    {
        System.out.println("父类普通代码块");
    }
    public parent(){
        System.out.println("父类构造方法");
    }
}

//子类sub
class sub extends parent{
    static{
        System.out.println("子类静态代码块");
    }
    {
        System.out.println("子类普通代码块");
    }
    public sub(){
        System.out.println("子类构造方法");
    }
    public static void main(String[] args) {
        System.out.println("main");
        new sub();
    }
}

输出结果为:

父类静态代码块
子类静态代码块
main
父类普通代码块
父类构造方法
子类普通代码块
子类构造方法

这里我们就可以知道,类中执行顺序的优先级为:静态代码块>main>普通代码块>构造方法

两次 main 在执行中的位置不同的原因有两个:1. 类被初始化后会导致该类及其父类中静态部分 (静态代码块、静态变量、main) 被加载 2. 新建类的实例会导致该类及其父类中静态部分(静态代码块、静态变量、main)和一些非静态部分(普通代码块、构造方法)被加载。

具体点说,情况1中 main 在子类和父类初始化之前,而情况2中 main 在子类和父类初始化之后。再提一句,如果 main 被放在父类中的话,那么整个子类就不会被初始化。

那如果没有“new sub()”会怎么样呢?

父类静态代码块
子类静态代码块
main

原因很简单,因为类只是被初始化了,而并没有关于它的实例被新建。这也是为什么非静态部分总体上一定会在静态部分后面的原因。

如果不了解类加载的过程,可以看我上一篇文章(【Java】Java语言的反射机制),里面有说明。

新增一个父类静态变量(情况3)

//父类parent
class parent {
    static{
        System.out.println("父类静态代码块");
    }
    private static parent a = new parent(); //父类静态变量
    {
        System.out.println("父类普通代码块");
    }
    public parent(){
        System.out.println("父类构造方法");
    }
}

//子类sub
class sub extends parent{
    static{
        System.out.println("子类静态代码块");
    }
    {
        System.out.println("子类普通代码块");
    }
    public sub(){
        System.out.println("子类构造方法");
    }
    public static void main(String[] args) {
        System.out.println("main");
        new sub();
    }
}

输出结果为:

父类静态代码块
父类普通代码块
父类构造方法
子类静态代码块
main
父类普通代码块
父类构造方法
子类普通代码块
子类构造方法

我们只是在父类中多加了一个静态变量,为什么父类静态代码块下面就变成了父类普通代码块和父类构造方法了呢?

原因有四个:1. 类中存在静态变量时的执行顺序优先级为:父类静态代码块和父类静态变量>子类静态代码块和子类静态变量>main>父类普通代码块>父类构造方法>子类普通代码块>子类构造方法 2. 静态代码块和静态变量只会被执行一次 3. 类只要被用到了(初始化)就一定会加载静态部分 4. 非静态部分只有被用到了(执行了)才会被加载

逻辑上顺序为:

父类静态代码块
//以下三行是由于父类被初始化赋值给了父类静态变量所产生的,自己中执行自己,有点递归的思想
父类静态代码块(不存在) //属于生成父类静态变量步骤(这一行由于父类静态代码块只会被执行一次所以并不存在)
父类普通代码块 //属于生成父类静态变量步骤
父类构造方法 //属于生成父类静态变量步骤
子类静态代码块
main
父类普通代码块
父类构造方法
子类普通代码块
子类构造方法

以上执行步骤可以简单整合为:

父类静态代码块+父类静态变量
子类静态代码块
main
父类普通代码块
父类构造方法
子类普通代码块
子类构造方法

这样就和情况2是一样的执行顺序了,只要用递归的思想去理解并不难懂。

将父类静态代码块和父类静态变量调换顺序(情况4)

//父类parent
class parent {
    private static parent a=new parent(); //父类静态变量
    static{
        System.out.println("父类静态代码块");
    }
    {
        System.out.println("父类普通代码块");
    }
    public parent(){
        System.out.println("父类构造方法");
    }
}

//子类sub
class sub extends parent{
    static{
        System.out.println("子类静态代码块");
    }
    {
        System.out.println("子类普通代码块");
    }
    public sub(){
        System.out.println("子类构造方法");
    }
    public static void main(String[] args) {
        System.out.println("main");
        new sub();
    }
}

输出结果为:

父类普通代码块
父类构造方法
父类静态代码块
子类静态代码块
main
父类普通代码块
父类构造方法
子类普通代码块
子类构造方法

我们只是将父类静态代码块和父类静态变量调换顺序,为什么父类普通代码块最先执行呢?

原因有两个:1. 父类静态代码块和父类静态变量之间的先后顺序由书写顺序决定 2. 静态代码块和静态变量只会被执行一次

我们来看看这段代码逻辑上的执行顺序:

整合版:
父类静态变量
父类静态代码块
子类静态代码块
main
父类普通代码块
父类构造方法
子类普通代码块
子类构造方法

详细版:
父类静态代码块(不存在) //属于生成父类静态变量步骤(这一行由于父类静态代码块只会被执行一次所以并不存在)
父类普通代码块 //属于生成父类静态变量步骤
父类构造方法 //属于生成父类静态变量步骤
父类静态代码块
子类静态代码块
main
父类普通代码块
父类构造方法
子类普通代码块
子类构造方法

以上步骤中由于父类静态代码块只会被执行一次,所以两个父类静态代码块只会存在一个。再加上之前提到的一条规则 —— 父类静态代码块和父类静态变量之间的先后顺序由书写顺序决定,所以只有第二次的父类静态代码块被执行了。

那可能有人要说,只会被执行一次的话,肯定后执行的发现之前执行过了于是不执行了。其实这里的先后顺序并不完全是参照时间意义上的,我们来做个实验:

public class Test2{
    static{
        a = -1;
    }
    private static int a;
    public static void main(String[] args){
        return;
    }
}

以上这段代码先赋值再声明,但它的编译和运行都没有问题。

所以实际上情况4的逻辑顺序可以更加细化:

父类静态代码块(不存在) //属于生成父类静态变量步骤
//以上一行由于父类静态代码块只会被执行一次,并且父类静态变量的书写顺序在父类静态方法的前面,所以现在父类静态代码块不能被执行
父类普通代码块 //属于生成父类静态变量步骤
父类构造方法 //属于生成父类静态变量步骤
父类静态代码块
子类静态代码块
main
父类普通代码块
父类构造方法
子类普通代码块
子类构造方法

总结一下影响Static执行顺序的所有规则

1. 类中执行顺序的优先级为:父类静态代码块和父类静态变量>子类静态代码块和子类静态变量>main>父类普通代码块>父类构造方法>子类普通代码块>子类构造方法

2. 其中,父类静态代码块和父类静态变量之间的先后顺序由书写顺序决定

3. 静态代码块和静态变量只会被执行一次

4. 类被初始化后会导致该类及其父类中静态部分 (静态代码块、静态变量、main) 被加载

5. 新建类的实例会导致该类及其父类中静态部分(静态代码块、静态变量、main)和一些非静态部分(普通代码块、构造方法)被加载

最开始的例子

//父类parent
class parent {
    private static parent a=new parent(); //父类静态变量
    static{
        System.out.println("父类静态代码块");
    }
    {
        System.out.println("父类普通代码块");
    }
    public parent(){
        System.out.println("父类构造方法");
    }
}

//子类sub
class sub extends parent{
    private static sub b = new sub(); //子类静态变量
    static{
        System.out.println("子类静态代码块");
    }
    {
        System.out.println("子类普通代码块");
    }
    public sub(){
        System.out.println("子类构造方法");
    }
    public static void main(String[] args) {
        System.out.println("main");
        new parent();
        System.out.println("parent-sub");
        new sub();
    }
}

逻辑上顺序为:

整合版:
父类静态变量
父类静态代码块
子类静态变量
子类静态代码块
main

详细版:
父类静态代码块(不存在)  //“private static parent a=new parent()”中父类静态变量步骤
父类普通代码块  //“private static parent a=new parent()”中父类静态变量步骤
父类构造方法  //“private static parent a=new parent()”中父类静态变量步骤
父类静态代码块
父类静态代码块(不存在)  //“private static sub b = new sub()”中子类静态变量步骤
父类普通代码块  //“private static sub b = new sub()”中子类静态变量步骤
父类构造方法  //“private static sub b = new sub()”中子类静态变量步骤
子类静态代码块(不存在)  //“private static sub b = new sub()”中子类静态变量步骤
子类普通代码块  //“private static sub b = new sub()”中子类静态变量步骤
子类构造方法  //“private static sub b = new sub()”中子类静态变量步骤
子类静态代码块
main
父类静态变量(不存在) //“main”中“new parent()”步骤
父类静态代码块(不存在)  //“main”中“new parent()”步骤
父类普通代码块  //“main”中“new parent()”步骤
父类构造方法  //“main”中“new parent()”步骤
parent-sub
父类静态变量(不存在) //“main”中“new sub()”步骤
父类静态代码块(不存在)  //“main”中“new sub()”步骤
父类普通代码块  //“main”中“new sub()”步骤
父类构造方法  //“main”中“new sub()”步骤
子类静态变量(不存在) //“main”中“new sub()”步骤
子类静态代码块(不存在)  //“main”中“new sub()”步骤
子类普通代码块  //“main”中“new sub()”步骤
子类构造方法  //“main”中“new sub()”步骤

相关文章

【Java】Java语言的反射机制

【Java】重写比较器java.util.Comparator的注意事项

【Java】String与各种数据类型之间的转换

暂无评论

请到【后台 - 用户 - 我的个人资料】中填写个人说明。

发表评论