java类初始化和对象初始化_你可能误解了的类初始化和对象初始化

不知道大家有没有这么一种经验——无论遇到的是新旧知识,经常地会以为自己掌握了,但当换另一种问法,或者增加一点难度的问题,往往我们会手足无措,不知所云。这段时间非常自信自己已经掌握了一个知识点,隔段时间才发现之前自己的理解其实完全是错误的,当初愉悦自信的心情依稀记于心中,而此刻对比起来却是啪啪啪把脸打的很疼,心情也变得很低落。现在的我可以肯定——自己太功利浮躁,懒于思考。意识到这种现象后,为了变得更好,与自己和解,脚踏实地花时间死磕吧,一开始进步可能很慢,时间久了,相信一定会有突破的~

那今天要提的一个现象呢,是我在学习Java类加载机制时候碰到的。关于详细的Java类加载知识的输出,我还需要一段时间的整合。这篇文章先做个小记录。

额外插入一个注意事项——静态代码块和静态方法不是同一个东西。其实基础常常不会难,只是我们太过功利焦急,囫囵吞枣,把很多概念混淆了,后来越来越乱。在写法上,下图前3行就是静态代码块,后3行是静态方法。二者都是在类加载的时候初始化的,区别就是——静态代码块是自动执行的,而静态方法是被调用的时候才执行的,比如Test.function(); 。

d0c29f9007c3

网上看到很多篇文章写了几个小小的demo,然后根据运行结果总结了静态代码块、构造代码块和构造方法的执行顺序——静态代码块>非静态代码块>构造方法,完事。原本我也以为自己背下这个结论就万事大吉了。直到我遇到了下面这个例子,我才开始反思,当我在看别人博客的时候,我在看什么。花两分钟想想下面代码的运行结果会是什么呢?

d0c29f9007c3

当亲自码代码并运行代码后,我对运行结果充满了疑惑。

d0c29f9007c3

运行结果

网上的文章不都说执行顺序是静态代码块>非静态代码块>构造方法吗?虽然这个代码里没模拟输出构造方法(大家可以自己加上试试),但为什么结果是三行输出,并且第一行结果是normal?难道网上文章的总结是错误的?其实不尽然,我掌握的知识不够全面,以至于没法判断导致此现象的原因,更别说举一反三了。

当Java源代码(.java文件)被编译器编译成Java字节码(.class文件)时,会自动产生两个方法,一个是类的初始化方法,另一个是对象实例的初始化方法。但需要注意的是,并不是所有的类的.class文件中都拥有一个()的——如果类没有声明任何类变量(类变量即静态变量,被static修饰的变量。类的成员变量包含静态变量和普通成员变量),也没有静态初始化语句,那么就不会有();如果类仅包含静态final变量的类变量初始化语句,并且这类类变量初始化语句采用编译时常量表达式,则类也不会有方法。

d0c29f9007c3public class Extended {

static final int age = 18;

static final int height = age*10;

}

类Extended声明了两个常量——age和height,并赋了初始值,但这两个字段并没有被当做类变量,而是被Java编译器特殊处理了,因为被final修饰了,被当做常量。JVM在使用了它们的任何类的常量池或者字节码流中直接存放的是它们表示的常量的 int 值。

另外需要注意的一点是,Java编译器为它编译的每个类中的构造方法都产生一个方法。我们可以想想,一个类可以有很多构造函数,所以一个类的.class文件中至少生成这样一个方法(即无参默认构造方法)。

好,就算上面的内容你并不能记忆,也不影响接下来内容的掌握。只是多了解点片块知识,有利于搭建更大的知识模块,到时候融会贯通,感受设计之美。现在我们来谈谈说方法和()方法都是什么时候被调用的。这是关键点。

():在JVM第一次加载.class文件到内存时调用。包括静态变量初始化语句(如第9行和第11行)和静态代码块(如第16~18行)的执行。

():在对象实例创建出来的时候调用。

对应到我们上面的代码例子要怎么理解呢?别急,我们再了解一些背景——JVM的其中一个工作内容是借助类装载器子系统将.class文件读取到内存中的。我们知道.class文件是一种8位字节的二进制流文件,JVM装载一个.class文件时,它会从这个二进制数据中解析类型信息,然后把这些类型信息放到方法区中。方法区中存有类变量(类变量即静态变量)和方法等。而当程序运行时,JVM会把所有该程序在运行时创建的对象都放到堆中。

d0c29f9007c3

一般情况下,静态随着类的加载而加载,而且优先于对象的存在。回过头来对应我们上面的代码。在走19行入口之前会先加载Test这个类。那怎么加载呢,按照实际代码写的内容,并且先执行静态内容,或者我们可以换个说法,()中有的内容是第9、11、16~18行。而()中是13~15行内容。因为在19行入口前先加载Test这个类,所以先执行(),而当加载完毕,开始执行20行代码时,就调用()。

但是照我这么说,还是不明白为什么运行结果先输出了“normal”?那当然了,我还没讲到这点。

d0c29f9007c3

d0c29f9007c3

代码从上到下,发现第9行是类变量,放到方法区去,然后再按顺序寻找其他静态内容。我们可以看到第11行的时候,创建了一个static的Test类对象test。咋办呀咋办呀,它好像很特别?不按常理出牌?是static,但是又是新创建的对象?怎么不是一个基本类型?这样可怎么整呀?其实很简单,我们上面说了():在对象实例创建出来的时候调用。那淡定地在11行调起()就好。那()里面有什么内容?就是13~15行的代码了,输出“normal”。

我们继续看代码,执行完11行代码后,再寻找下一部分静态内容——16~18行,输出“static 1”。至此,Test类里没有静态内容了。加载结束,该进入第19行了,到第20行的时候,发现又创建了一个Test对象,那怎么做?调用()呀,即又跑了一遍13~15行的代码,输出“normal”了。

一点小建议:可以在第20行之前再写一行代码System.out.println("==========");

大家还可以试试在Test类中增加构造函数的代码,并且构造函数里面做类似的输出,看看会是什么样的结果。

至此,我们把文中的代码运行结果原因讲了一遍。不知道对大家有没有帮助。这篇文章看起来不长,但是真的写了很久很久。我一直在斟酌如何措辞和描述现象。就连标题我都不确定自己是不是起的准确,很难做到尽善尽美,我大概了解代码运行涉及到的流程,但有很多细枝末节的知识点是我暂时没法确认的,尽管我自己会猜测一些原因,也相信自己的猜测是正确的,但我没有在书上或者其他地方找到确凿的证据,不敢直接在文章中告诉大家——事实就是blabla。给大家推荐一本书 文纳斯的《深入Java虚拟机》,这本书我看了两遍,重点章节翻了好几次,不过我觉得还可以再多阅读阅读。周志明也写了一本《深入理解Java虚拟机》,不知道内容怎么样,但豆瓣上的评分也挺高的。

关于虚拟机涉及到的相关内容,我后面还会再做输出,如有不正确的地方,还请大家批评指正,我们共同进步,谢谢~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值