接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析
接口初始化规则:
在之前的例子中都是围绕类来进行的,这次来看一下接口的初始化相关的东东,直接新建一个例子:
此时编写main方法去调用接口里面的字段:
照着之前【http://www.cnblogs.com/webor2006/p/8835953.html】类的例子来看,如果主动使用了子类那其它的父类也会被初始化,而调用静态字段时它的类也就会被主动使用,回忆一下:
那对于接口,里面没法给static静态代码块来验证该类是否初始化了,那如何整呢?这时先运行一下:
此时会生成字节码文件,然后将MyParent5的字节码文件给删掉,再运行:
所以这里说明一个问题:当一个接口在初始化时,并不要求其父接口都完成了初始化。
那如果将MyChild5的字节码文件也删掉呢?看结果:
同样能运行,那不跟之前做的编译期常量放在常量的类似,呃,常量?问题是在接口中木有声明为final呀:
那加上呗:
标灰了,很显然对于接口而言定义的字段都是常量的,所以如果将字段声明为运行期常量时再删除字节码文件肯定会报错,不信试试?
确实如此,因为这种运行期的常量是不会被放在常量池当中的,那继续对程序进行改造:
此时再将MyParent5的字节码文件删掉,照之前的实验应该还是能正常输出的,那结果:
抛异常了!!那如果将MyParent5中的常量声明为编译期常量结果又会是如何呢?
依然抛异常,所以这里又可以总结一个东东:只有在真正使用到父类接口的时候(如引用接口中所定义的常量时),才会初始化。
接下来继续改造:
然后再将MyParent5删掉:
因为MyChild5中使用的是常量池的数据,那如果将接口此时改成类呢,在改之前先将final关键字去掉:
而由于接口中的字段不声明final其实也是常量,所以将字节码MyParent5删掉肯定也是能正常运行的,那接着将其改为class,其它都不变:
此时再将MyParent5的字节码文件删掉:
此时就抛异常了,因为对于类来说如果木有声明为final类型就不认为是常量,很显示在初始化子类时肯定也要初始化父类,那当然就抛异常啦,做这些实验就是为了说明之前总结的:当一个接口在初始化时,并不要求其父接口都完成了初始化,只有在真正使用到父类接口的时候(如引用接口中所定义的常量时),才会初始化。
类加载器准备阶段和初始化阶段的重要意义分析:
下面再来编写一个新的例子,如下:
那其结果是多少呢?比较容易理解,如下:
好!!接下来稍改造一下代码:
那此时的结果又是多少呢?运行:
呃~~这是为何,下面打一些日志进一步观测,就会有新大陆发现:
编译运行:
实际上在私有构中是counter2是1,只是由于将counter2的静态声明放在它之后了又被置为0了而已,那为什么会这样呢?这里还得从类的加载顺序来分析,具体如下:
由于咱们在main方法中主动调用了类中的静态方法,如下:
而根据主动使用的七种方式,它属于这种:
所以会导致Singleton类的初始化,而在初始化之前还会有一个准备阶段,如:
那准备阶段干了啥事呢【对于这个阶段接触的少,但是很重要!】?由上往下准备,所以也按上往下的顺序分析,具体如下:
好,整个类的准备阶段完成,接着准备类的初始化了,而在初始化阶段的时候会给类中的静态变量赋初值,也是按从上往下的顺序执行的,所以:
好,关键的地方来了:
那通过上面从准备到初始化阶段的分析,那准备阶段的意义何在呢?看:
好~~理解了上面代码的整个过程,接着再来修改一下程序,结果又会是怎样,如下:
理解清楚了之前的那个例子,我想对于这次的修改结果很容易就可以推出来了,这里直接上结果:
【说明】:上面这个程序没有什么实用价值,但是对于理解类的加载过程是极其有帮助的。