java开发:静态代码块、构造代码块执行时期与顺序

今天突发奇想,静态代码块、构造代码块和构造函数执行时期和顺序是怎样的呢?于是我写了的demo测试了一下


/**
 * @Author: david.lvfujiang
 * @Date: 2020/1/12
 * @Describe:
 */
public class User {
    static {
        Log.e("tag","静态代码块");
    }
    {
        Log.e("tag","构造代码块");
    }

    public User() {
        Log.e("tag","构造函数");
    }
}

User实体类中有静态的代码块、构造代码块、构造函数

public class Main6Activity extends AppCompatActivity {


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main6);
        System.out.println(User.class);
    }
}

我在主函数调用 System.out.println(User.class);发现控制台什么都没输出。

public class Main6Activity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main6);
        try {
            Class user = Class.forName("com.example.User");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

我通过反射获取User对象,发现控制台输出了:
2020-01-12 18:02:59.661 2038-2038/com.example.serializationapplication E/tag: 静态代码块

public class Main6Activity extends AppCompatActivity {


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main6);
      User user = new User();
    }
}

我直接创建User对象,控制台输出:
2020-01-12 18:04:05.301 3080-3080/com.example.serializationapplication E/tag: 静态代码块
2020-01-12 18:04:05.301 3080-3080/com.example.serializationapplication E/tag: 构造代码块
2020-01-12 18:04:05.301 3080-3080/com.example.serializationapplication E/tag: 构造函数

因此就有了一个问题:静态代码块到底是什么时候执行的,是类加载的时候就调用还是在使用类的时候才会调用? 构造代码块和构造方法又有什么样的关系?
百度看到了一篇解释的很好的文章:java的static块执行时机

总结一下就是类在加载过程中会经历几个阶段:

1.加载
2.验证
3.准备
4.解析
5.初始化

每一个阶段具体执行的工作可以参考这篇博客:深入理解Java虚拟机笔记—类加载过程

而静态代码块是在类加载的最后一个阶段:初始化阶段执行的。初始化阶段主要工作就是对类变量进行赋值和执行静态代码块。
对于初始化阶段,虚拟机规范则是严格规定了有且只有四种情况必须立即对类进行“初始化”(而加载,验证,准备阶段自然需要在此之前开始):

1.使用new关键字实例化对象的时候,读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
2.使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
3.当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
4.当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。

因此回顾上诉的三个例子,我们可以知道第一次调用: System.out.println(User.class);虽然User类被加载了,但是并不属于主动使用的范围,因此不会进行初始化,则静态代码块也不会被执行
第二次调用则是使用了反射,第三次直接new对象,都是属于主动调用,因此执行了静态代码块,而且静态代码块只执行了一次。
最后补充:静态代码块是优先执行于构造代码块的。即静态代码块>构造代码块>构造函数。
而构造代码块是依托于构造函数,构造函数被执行时会先调用构造代码块。构造函数不被调用则构造代码块也不会被调用

总结:

静态代码块是在类初始化阶段执行的,而类初始化阶段是类加载的最后一个阶段。当类属于被动调用时,类加载过程不执行初始化,而如果是主动调用的则会执行初始化:

1.使用new关键字实例化对象的时候,读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
2.使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
3.当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
4.当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。

发布了194 篇原创文章 · 获赞 42 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_39027256/article/details/103947917
今日推荐