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

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

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

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

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

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

运行结果

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

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

public class Extended {

      static final int age = 18;

        static final int height = age*10;

}

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

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

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

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

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

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

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

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

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

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

一点小建议:可以在第20行之前再写一行代码

System.out.println("==========");

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

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,497评论 18 399
  • 搜索的时候看了好几篇文,自己就想记录一遍,加深一下记忆,以下是原文的地址,受益匪浅。blog.csdn.net/n...
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,497评论 18 139
  • 消费钱包适度花 消费是为你的衣食住行买单。 把钱花在效用高的事情上 花得省不算花得值,效用高才真的值。 把钱花在今...
    Su公子阅读 241评论 0 0
  • “到最后仍然是放不下那种别扭,就把这些琐碎随笔写下,却希望你别太看重。我写小说时有种莫名的慎重,不肯让我的人物活得...
    小远姑娘阅读 337评论 0 0