接口初始化规则:

接着上一次【​​编译期常量与运行期常量的区别及数组创建本质分析​​】继续往下,在之前的例子中都是围绕类来进行的,这次来看一下接口的初始化相关的东东,直接新建一个例子:

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_初始化

此时编写main方法去调用接口里面的字段:

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_字节码_02

照着之前类的例子来看,如果主动使用了子类那其它的父类也会被初始化,而调用静态字段时它的类也就会被主动使用,回忆一下:

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_字段_03

那对于接口,里面没法给static静态代码块来验证该类是否初始化了,那如何整呢?这时先运行一下:

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_字段_04

此时会生成字节码文件,然后将MyParent5的字节码文件给删掉,再运行:

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_jvm_05

所以这里说明一个问题:当一个接口在初始化时,并不要求其父接口都完成了初始化。

那如果将MyChild5的字节码文件也删掉呢?看结果:

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_初始化_06

同样能运行,那不跟之前做的编译期常量放在常量的类似,呃,常量?问题是在接口中木有声明为final呀:

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_初始化_07

那加上呗:

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_初始化_08

标灰了,很显然对于接口而言定义的字段都是常量的,所以如果将字段声明为运行期常量时再删除字节码文件肯定会报错,不信试试?

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_初始化_09

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_初始化_10

确实如此,因为这种运行期的常量是不会被放在常量池当中的,那继续对程序进行改造:

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_字段_11

此时再将MyParent5的字节码文件删掉,照之前的实验应该还是能正常输出的,那结果:

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_jvm_12

抛异常了!!那如果将MyParent5中的常量声明为编译期常量结果又会是如何呢?

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_jvm_13

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_初始化_14

依然抛异常,所以这里又可以总结一个东东:只有在真正使用到父类接口的时候(如引用接口中所定义的常量时),才会初始化。

接下来继续改造:

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_字节码_15

然后再将MyParent5删掉:

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_jvm_16

因为MyChild5中使用的是常量池的数据,那如果将接口此时改成类呢,在改之前先将final关键字去掉:

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_jvm_17

而由于接口中的字段不声明final其实也是常量,所以将字节码MyParent5删掉肯定也是能正常运行的,那接着将其改为class,其它都不变:

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_初始化_18

此时再将MyParent5的字节码文件删掉:

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_字段_19

此时就抛异常了,因为对于类来说如果木有声明为final类型就不认为是常量,很显示在初始化子类时肯定也要初始化父类,那当然就抛异常啦,做这些实验就是为了说明之前总结的:当一个接口在初始化时,并不要求其父接口都完成了初始化,只有在真正使用到父类接口的时候(如引用接口中所定义的常量时),才会初始化。

类加载器准备阶段和初始化阶段的重要意义分析:

下面再来编写一个新的例子,如下:

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_字节码_20

那其结果是多少呢?比较容易理解,如下:

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_字节码_21

好!!接下来稍改造一下代码:

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_字节码_22

那此时的结果又是多少呢?运行:

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_jvm_23

呃~~这是为何,下面打一些日志进一步观测,就会有新大陆发现:

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_jvm_24

编译运行:

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_初始化_25

实际上在私有构中是counter2是1,只是由于将counter2的静态声明放在它之后了又被置为0了而已,那为什么会这样呢?这里还得从类的加载顺序来分析,具体如下:

由于咱们在main方法中主动调用了类中的静态方法,如下:

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_字段_26

而根据主动使用的七种方式,它属于这种:

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_初始化_27

所以会导致Singleton类的初始化,而在初始化之前还会有一个准备阶段,如:

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_jvm_28

那准备阶段干了啥事呢【对于这个阶段接触的少,但是很重要!】?由上往下准备,所以也按上往下的顺序分析,具体如下:

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_jvm_29

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_字节码_30

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_字段_31

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_jvm_32

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_字节码_33

好,整个类的准备阶段完成,接着准备类的初始化了,而在初始化阶段的时候会给类中的静态变量赋初值,也是按从上往下的顺序执行的,所以:

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_jvm_34

好,关键的地方来了:

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_字节码_35

那通过上面从准备到初始化阶段的分析,那准备阶段的意义何在呢?看:

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_字段_36

好~~理解了上面代码的整个过程,接着再来修改一下程序,结果又会是怎样,如下:

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_字节码_37

理解清楚了之前的那个例子,我想对于这次的修改结果很容易就可以推出来了,这里直接上结果:

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_jvm_38

【说明】:上面这个程序没有什么实用价值,但是对于理解类的加载过程是极其有帮助的。







关注个人公众号,获得实时推送

接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析_jvm_39