今天突发奇想,静态代码块、构造代码块和构造函数执行时期和顺序是怎样的呢?于是我写了的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()方法的那个类),虚拟机会先初始化这个主类。