Java类的初始化

之前整理了《JVM之类加载机制》的文章,对于一个类的初始化阶段介绍太过简略,这里再开一篇文章,着重介绍类的初始化流程。

类初始化是类加载过程的最后一个阶段,到初始化阶段,才真正开始执行类中的Java程序代码。虚拟机规范严格规定了有且只有四种情况必须立即对类进行初始化

  • 遇到new、getstatic、putstatic、invokestatic这四条字节码指令时,如果类还没有进行过初始化,则需要先触发其初始化。生成这四条指令最常见的Java代码场景是:使用new关键字实例化对象时、读取或设置一个类的静态字段(static)时(被static修饰又被final修饰的,已在编译期把结果放入常量池的静态字段除外)、以及调用一个类的静态方法时。

  • 使用Java.lang.refect包的方法对类进行反射调用时,如果类还没有进行过初始化,则需要先触发其初始化。

  • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。

  • 当虚拟机启动时,用户需要指定一个要执行的主类,虚拟机会先执行该主类,也就是main方法所在的类。

上面所说时显示的对象创建,还有几种隐式初始化的方式也说明一下:
  • 给String类型变量赋值时,若String对象在常量池中不存在,则创建一个新的String对象

  • 对String对象进行拼接操作,同上

  • 自动装箱机制可能会引起一个原子类型的包装类对象被创建。

这里再说一下类不被初始化的情况:
  • 对于静态字段(没有final修饰),只有直接定义这个字段的类才会被初始化,子类调用父类的静态字段并不会触发子类的初始化

  • static final 修饰的常量,在编辑时就存入了调用者的Class文件常量池中,调用时并不会触发定义类的初始化,也就是这个常量已经使用的类绑定。

  • 数组初始化过程并不会触发引用类的初始化

类或接口初始化(准备阶段)

编译器自动收集类变量赋值以及静态代码块后自动合并生成类的<clint>(),类开始初始化时会为static变量赋上零值。

  • <clint>()对于类和接口来说这个方法并不是必须的。
  • <clint>()中,静态语句只能访问定义在它之前定义的静态变量,定义在它之后的静态变量,可以赋值,但不能访问。
  • 子类<clint>()不需要显示的调用父类的构造器,JVM保证子类的<clint>()执行之前,父类的<clint>()已经执行完毕。
  • 由于父类的<clint>()先执行,所以父类的静态语句优先与子类的静态语句执行
  • 先对类,接口的执行<clint>()时并不需要执行父接口的<clint>()方法,只有使用父接口定义的变量时,父接口才会初始化。接口的实现类初始化时也不会调用接口的<clint>()
  • JVM保证一个类的<clint>()执行时线程安全的,多线程执行类的<clint>()时只能有一个被执行,其余线程等待(执行完毕后其他线程不再进入<clint>())。如果一个类的<clint>()执行耗时操作,可能会造成多进程阻塞

这里还有几个注意点:

接口也有初始化过程,在接口中不能使用“static{}”语句块,但编译器仍然会为接口生成<clint>(),用于初始化接口中定义的成员变量(实际上是static final修饰的全局常量)。

二者在初始化时最主要的区别是:当一个类在初始化时,要求其父类全部已经初始化过了,但是一个接口在初始化时,并不要求其父接口全部都完成了初始化,只有在真正使用到父接口的时候(如引用接口中定义的常量),才会初始化该父接口。这点也与类初始化的情况很不同,调用类中的static final常量时并不会 触发该类的初始化,但是调用接口中的static final常量时便会触发该接口的初始化

类变量的赋值

下面简要说明下final、static、static final修饰的字段赋值的区别:

  • static修饰的字段在类加载过程中的准备阶段被初始化为0或null等默认值,而后在初始化阶段(触发类构造器<clint>())才会被赋予代码中设定的值,如果没有设定值,那么它的值就为默认值。

  • final修饰的字段在运行时被初始化(可以直接赋值,也可以在实例构造器中赋值),一旦赋值便不可更改;

  • static final修饰的字段在Javac时生成ConstantValue属性,在类加载的准备阶段根据ConstantValue的值为该字段赋值,它没有默认值,必须显式地赋值,否则Javac时会报错。可以理解为在编译期即把结果放入了常量池中。

实例对象的初始化(初始化阶段)

编译器自动收集实例变量初始化以及实例代码块后自动合并生成类的<init>()

  • 子类初始化时会先调用父类<init>(),用以保证子类能正常初始化。
  • 执行子类的<init>()

那么从上面可以知道,一个类在初始化过程中,构造方法的执行过程如下:

  • 父类的<clint>()
  • 子类的<clint>()
  • 父类的<init>()
  • 子类的<init>()

一个子类自身代码的初始化执行顺序如下:

  • Static Field Initial (类变量)

  • Static Patch Initial (静态初始化块)

  • Field Initial (成员变量)

  • Field Patch Initial (初始化块)

  • Structure Initial (构造器)

上面第一条和第二条依据代码定义的顺序不同,执行的顺序也不同(定义在静态代码块之后的的类变量可以被静态代码块赋值,但是不能被访问)

举个栗子验证一下

public class ClassInitTest {

    public static void main(String[] args) {
        new Child();
    }

    public static class Parent {
        static {
            System.out.println("Parent static code");
        }

        public Parent() {
            System.out.println("Parent constructor code");
        }
    }

    public static class Child extends Parent {
    
         private int mInt = 100;

        {
            System.out.println(mInt);
            mInt = 200;
            System.out.println(mInt);
        }
    
        static {
            System.out.println("Child static code");
        }

        public Child() {
            System.out.println("Child constructor code");
            System.out.println(mInt);
        }
    }
}

打印如下:

Parent static code
Child static code
Parent constructor code
100
200
Child constructor code
200

上面可以看到即使类变量定义在成语变量和初始代码块之下也是先被执行的,同时我们可以看到构造器的代码时最后执行的。

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

推荐阅读更多精彩内容

  • 一个Java对象的创建过程往往包括 类初始化 和 类实例化 两个阶段。本文讨论的是『类初始化』的时机,以及利用这一...
    NoahU阅读 2,091评论 0 2
  •   最近在看回顾Java基础的时候,发现看似很简单的类初始化的顺序却并不是那么简单(往往越是简单的东西反而越容易出...
    BrightLoong阅读 1,490评论 0 2
  • 在java中,一个类被使用要经过装载,连接,初始化这样的过程 一、装载: 类装载器(Bootstrap Class...
    暮雨沉沦阅读 595评论 0 0
  • 我和妈妈去超市买东西。刚进门准备寄存,就看到寄存柜前站着一对焦急的母女在急促地对话。 “我记得给你了。” “是啊,...
    andy花儿阅读 304评论 0 0
  • 生活,每个人心中都有一个不一样的理解,所以它是多样的,也是善变的。 你以什么样的态度来对待它,它将还你一个什么样的生活。
    米粒的世界阅读 237评论 1 1