初始化对于类与接口的异同点深入解析

在上一次【http://www.cnblogs.com/webor2006/p/8905978.html】中学习了类加载器理论的一些内容,其中回顾一下关于类的初始化时机的这段文字:

而截图中也说明了这个结论在之前篇幅中也已经详细说明过了,那再拧出来有何意义呢?因为以前举的例子并不能很好的说明上面的结论,为什么,先来回顾一下当时的代码:

其中当时是这么论证“当一个接口在初始化时,并不要求其父接口都完成了初始化”的,先运行程序:

然后将运行生成的MyParent5的字节码文件删除掉,再运行,其结果也正常可以打印,如下:

然后此时将程序修改一下再用相同的方法来运行:

然后再将生成的MyParent5的字节码文件删掉再运行:

此时就抛异常了,由此实验就得出了“当一个接口在初始化时,并不要求其父接口都完成了初始化”的结论了,但是!!删字节码的实验当时是为了说明编译期常量与运行期常量的一个区别的,回顾一下:

因为对于编译期常量在编译之后会将常量放到调用常量所在类MyTest3的常量池中,所以之后的运行就跟MyParent3这个类就木有关系啦,而对于运行期常量,如上面截图的就是属于运行期常量的情况。

其实这个实例只能说明“在对MyChild5类的主动使用时JVM会尝试着加载MyParent5这个接口,但是由于MyParent5已经被人为删除了所以就报类找不到了”,这里为了说明此问题还是在JVM中增加用来查看类加载的一个参数再来运行观察,如下:

重新编译运行:

而在上面的实验中因为将MyParent5的字节码文件删掉了,那当然就抛出类找不到的异常啦,好接下来将MyChild5还是改回来接口:

编译运行:

那此时跟MyParent5和MyChild5完全木有关系,那下面将这两个的class文件删掉,然后再运行:

是不是惊呆了,这也跟我们打印的加载类的结果是相符合的,只是加载了MyTest5这个类,这也是接口跟类的不同,如果MyChild5是类的话那就会加载,好!下面将编译期的变量改为运行期的变量,如下:

再来编译运行:

这个在之前就已经讲过了,不过这里再总结一下关于接口和类的初始化:

其根本是否是常量来决定加载的情况,好,那回到咱们想要重新来论证的主题上来:

重新论证1:“在初始化一个类时,并不会先初始化它所实现的接口”

对于接口咱们知道是没法跟类一样在静态代码块中加一些日志来看出是否它被初始化了,其实还是有办法的,这里先来了解一个java的基础语法,因为在咱们实验代码中是需要用到这个特性的,如下:

请问它的输出是什么?运行看一下:

也就是static块只会在类加载初始化阶段调用一次,好,接下来修改一下再来看:

再来看结果:

也就是说不加static块是在类被构造的时候才去初始化的,了解了这个语法之后,下面来将咱们的实验代码进行修改:

也就是在接口中可以定义一个变量,然后在初始化变量里面加一个初始化块,如果MyParent5初始化了,那它里面的thread变量肯定也需要被初始化,而只要一初始化那“MyParent5 invoked”就会从初始化块中打印出来,注意:类的初始化和类的准备是两个不同的阶段,千万不要搞混啦,那下面来运行看一下:

而主动去调用MyChild5.b很显示会导致MyChild5子类的初始化,那“在初始化一个类时,并不会先初始化它所实现的接口。”,这样就精确论证了这一点,好,那如果将接口改为class呢?

当然会呀,因为目前是父子关系了,如下:

那如果再变化:

这个前面多次提到了,肯定是不会的,因为它是编译器的常量会被放到MyTest5的常量池了,如下:

接下来继续修改代码:

编译运行:

这个结论如预想,那如果改成类呢?

重新论证2:“在初始化一个接口时,并不会先初始化它的父接口。”:

修改实验代码:

编译运行:

很显然在初始化MyParent5这个子接口时,其MyGrandpa并未进行初始化。

猜你喜欢

转载自www.cnblogs.com/webor2006/p/8922287.html
今日推荐