1.面试题:static与继承的结合

书接上文,在上一篇案例中,我们通过识别哪些static字段"不生成clinit方法”,进而理解了面试题中哪些static元素不会打印。

本文进一步分析那些会生成clinit方法,在哪些场景下会被调用。

2.场景1:创建类的实例

以下代码创建了类的实例:

image-20210319142108479

运行代码后,可以发现,类Test1生成了clinit方法被调用了。

image-20210319142237424

实验结论:

通过new方式,创建类的实例时,类中的clinit方法会被执行

聪明的读者,可以尝试以下方式创建类的实例,看看clinit方法是否会被执行

反射

克隆

反序列化

3.场景2:调用类的静态方法

以下代码演示了调用类的静态方法:

image-20210319143319830

运行代码后,可以发现字节码中出现了invokestatic指令,类Test3生成了clinit方法被调用了。

image-20210319143417477

实验结论:

调用类的静态方法时,类中的clinit方法会被执行

3.场景3:调用类、接口的静态字段

以下代码演示了调用类的静态字段:

image-20210319143711917

运行代码后,可以发现main函数中(说明:一次只放出一条打印语句),Test4的a2、A5、A6、A7会触发clinit方法,而A3、A4、A8不会触发clinit方法。

为什么呢?

因为A3、A4、A8就是上篇提到的不会生成clinit的场景。

实验结论:

调用类中静态字段,且静态字段会生成clinit时,类中的clinit方法会被执行

4.场景4:使用java.lang.reflect包中的方法反射类的方法时

以下代码演示了使用Class.forName:

image-20210319144451690

运行代码,可以发现clinit方法被执行了

image-20210319144543469

实验结论:

使用java.lang.reflect包中的方法反射类的方法时,类中的clinit方法会被执行

5.场景5:初始化子类

以下代码演示了创建子类的实例:

image-20210319144638827

运行代码,可以发现,依次执行了父类的clinit方法、子类的clinit方法

image-20210319144834224

实验结论:

初始化子类时,如果父类还没有初始化,则会触发父类的初始化

6.场景6:接口中定义了default方法

如下代码演示了在接口中定义default方法

image-20210319145731549

运行代码,可以发现,接口的clinit方法被执行了

实验结论:

如果接口中定义了default方法,clinit方法被执行

反过来思考,如果接口中没有定义default方法呢?读者可以删掉示例中的default方法,看看clinit方法是否会被执行。

7.总结

本文分析了Java实战中极为常用的关键词:static

  • javac自动生成clinit的原则:被static修饰的字段和代码块都可能触发自动生成clinit。
  • 不生成clinit的场景本质:被static final修饰、基础数据类型或String类型、赋值不涉及对象生成的场景,都不会触发自动生成clinit
  • 能够调用clinit的场景
    • 通过new方式创建类的实例
    • 调用类的静态方法
    • 调用类和接口的静态字段
    • 反射类
    • 初始化子类
    • 接口中定义default方法

image-20210304003623960

8.参考资料

《深入理解Java虚拟机》的类加载章节-周志明