1.面试题:static与继承的结合
书接上文,在上一篇案例中,我们通过识别哪些static字段"不生成clinit方法”,进而理解了面试题中哪些static元素不会打印。
本文进一步分析那些会生成clinit方法,在哪些场景下会被调用。
2.场景1:创建类的实例
以下代码创建了类的实例:
运行代码后,可以发现,类Test1生成了clinit方法被调用了。
实验结论:
通过new方式,创建类的实例时,类中的clinit方法会被执行
聪明的读者,可以尝试以下方式创建类的实例,看看clinit方法是否会被执行
反射
克隆
反序列化
3.场景2:调用类的静态方法
以下代码演示了调用类的静态方法:
运行代码后,可以发现字节码中出现了invokestatic指令,类Test3生成了clinit方法被调用了。
实验结论:
调用类的静态方法时,类中的clinit方法会被执行
3.场景3:调用类、接口的静态字段
以下代码演示了调用类的静态字段:
运行代码后,可以发现main函数中(说明:一次只放出一条打印语句),Test4的a2、A5、A6、A7会触发clinit方法,而A3、A4、A8不会触发clinit方法。
为什么呢?
因为A3、A4、A8就是上篇提到的不会生成clinit的场景。
实验结论:
调用类中静态字段,且静态字段会生成clinit时,类中的clinit方法会被执行
4.场景4:使用java.lang.reflect包中的方法反射类的方法时
以下代码演示了使用Class.forName:
运行代码,可以发现clinit方法被执行了
实验结论:
使用java.lang.reflect包中的方法反射类的方法时,类中的clinit方法会被执行
5.场景5:初始化子类
以下代码演示了创建子类的实例:
运行代码,可以发现,依次执行了父类的clinit方法、子类的clinit方法
实验结论:
初始化子类时,如果父类还没有初始化,则会触发父类的初始化
6.场景6:接口中定义了default方法
如下代码演示了在接口中定义default方法
运行代码,可以发现,接口的clinit方法被执行了
实验结论:
如果接口中定义了default方法,clinit方法被执行
反过来思考,如果接口中没有定义default方法呢?读者可以删掉示例中的default方法,看看clinit方法是否会被执行。
7.总结
本文分析了Java实战中极为常用的关键词:static
- javac自动生成clinit的原则:被static修饰的字段和代码块都可能触发自动生成clinit。
- 不生成clinit的场景本质:被static final修饰、基础数据类型或String类型、赋值不涉及对象生成的场景,都不会触发自动生成clinit
- 能够调用clinit的场景
- 通过new方式创建类的实例
- 调用类的静态方法
- 调用类和接口的静态字段
- 反射类
- 初始化子类
- 接口中定义default方法
8.参考资料
《深入理解Java虚拟机》的类加载章节-周志明