Java类的初始化顺序

title picture
title picture

  最近在看回顾Java基础的时候,发现看似很简单的类初始化的顺序却并不是那么简单(往往越是简单的东西反而越容易出错呢),所以我觉得还是把它写下来,作为自己的备忘录比较好。既然都记录了我觉得我还是记录得比较全面的较好,所以显得有点啰嗦。

普通类的初始化(不存在继承,内部类的时候)

为了更详细的验证类的初始化顺序,首先我创建了一个被另一个类使用的类B.java

public class B {
    private int varOneInB = initInt("varOneInB"); // 6 14
    private static int staticVarOneInB = initInt("staticVarOneB");  // 4  
    private int varTwoInB = initInt("varTwoInB"); // 7 15
    private static int staticvarTwoInB = initInt("staticvarTwoInB"); // 5
    
    /**
     * 构造方法
     */
    public B() {
        System.out.println("B  constructor"); // 8 16
    }
    
    /**
     * 用于对int类型的变量赋值
     * @param varName
     * @return
     */
    private static int initInt(String varName) {
        System.out.println(varName + " init");
        return 2017;
    }
}

然后我创建了一个A类来验证初始化顺序,并且在该类中同时使用的static变量和static块等。

public class A {
    private int varOneInA = initInt("varOneInA"); // 11
    private static int staticVarOneInA = initInt("staticVarOneInA"); // 1 
    {
          int varTwoInA = initInt("varTwoInA"); // 12
    }
    static {
          int staticvarTwoInA = initInt("staticvarTwoInA");  // 2
    }
    private B b = new B(); // 13
    private static B staticB = new B(); // 3
    
    /**
     * 构造方法
     */
    public A() {
        System.out.println("A  constructor"); // 17
    }
    
    /**
     * 用于对int型变量赋值
     * @param varName
     * @return
     */
    private static int initInt(String varName) {
        System.out.println(varName + " init");
        return 2017;
    }
    
    public void run() {
        System.out.println("run be called");// 23
    }
    
    public static void main (String[] args) {
        System.out.println("start running");// 9
        A a = new A();// 10
        a.run();// 18
    }
}

运行后结果为:

staticVarOneInA init
staticvarTwoInA init
staticVarOneB init
staticvarTwoInB init
varOneInB init
varTwoInB init
B  constructor
start running
varOneInA init
varTwoInA init
varOneInB init
varTwoInB init
B  constructor
A  constructor
run be called

对《Think in java》这本书里面的关于初始化顺序的总结进行归纳如下:

注意:即使变量定义散布于方法定义之间,它们仍旧会在任何方法(包括构造器)被调用之前得到初始化。

  1. 即使没有显示地使用static关键字,构造器实际上也是静态方法。因此,当首次创建类的对象时(构造器可以看出静态方法),或者类的静态方法/静态域被首次访问时,Java解释器必须查找类路径。
  2. 然后载入class,有关静态初始化的所有动作都会执行(所以静态初始化只在Class对象首次被加载的时候进行一次)。
  3. 当使用new创建对象的时候,首先将在堆上为对象分配足够的存储空间。
  4. 这块存储空间会被清零,这就自动将Dog对象中的所有基本类型数据都设置成了默认值(对数字来说就是0,对布尔类型和字符类型也相同),而引用就则被设置成了null。
  5. 执行出现于字段定义出的初始化动作。
  6. 执行构造器。

有了上面的知识点,再来看上面的结果。我用数字1 2 3做了标记,括号后的阿拉伯数字表示上面代码对应的地方。

  • 在类A中执行main方法,由于main()是静态方法,必须加载A类,然后起静态域staticVarOneInA(1),staticvarTwoInA(2),staticB(3)被初始化。
  • 在staticB被初始化的时候,导致B类被加载,因为是第一次加载,对静态域进行初始化,因此staticVarOneInB(4),staticvarTwoInB(5)被初始化。
  • 之后顺序初始化varOneInB(6),varTwoInB(7),执行构造器B(8)。
  • 在A静态域初始化后,回到main()方法,打印出了“start running”(9),在new A()(10)的时候,分配a对象的空间,开始顺序初始化varOneInA(11),varTwoInA(12)和b(13),初始化b的时候因为不是第一次加载,所以staticVarOneInB,staticvarTwoInB不再被初始化,只是初始化了varOneInB(14),varTwoInB(15),然后执行构造B(16)。
  • 初始化完成后,调用A的构造器(17);最后通过a.run()调用run(18)方法,打印出“run be called”。

具有继承的类的初始化

下面,我创建了一个Father类和一个继承Father类的Son类,来探究在有继承的时候类的初始化和加载,情况基本和上面类似,我就不再写太多的注释了。Father类如下:

public class Father {
    private int varInFather = initInt("varInFather");
    private static int staticVarInFather = initInt("staticVarInFather");
    
    public Father(String name) {
        System.out.println("Father constructor" + " name:" + name);
    }
    
    private static int initInt(String varName) {
        System.out.println(varName + " init");
        return 2017;
    }
}

Son.java如下:

public class Son extends Father{
    private int varInSon = initInt("varInSon");
    private static int staticVarInSon = initInt("staticVarInSon");
    
    public Son(String name) {
        super(name);
        System.out.println("Son constructor" + " name:" + name);
    }
    
    private static int initInt(String varName) {
        System.out.println(varName + " init");
        return 2017;
    }
    
    public static void main(String[] args) {
        System.out.println("start running");
        Son son = new Son("Bob");
    }
}

输出结果如下;

staticVarInFather init
staticVarInSon init
start running
varInFather init
Father constructor name:Bob
varInSon init
Son constructor name:Bob

同样的,我将《Think in java》中的关于继承的类加载和初始化归纳如下:

注意:即使变量定义散布于方法定义之间,它们仍旧会在任何方法(包括构造器)被调用之前得到初始化。

  1. (同上)即使没有显示地使用static关键字,构造器实际上也是静态方法。因此,当首次创建类的对象时(构造器可以看出静态方法),或者类的静态方法/静态域被首次访问时,Java解释器必须查找类路径,在对它进行加载的过程中,编译器注意到它有一个基类(有extends得知),于是它继续加载,不管你时候打算产生一个该基类的对象。如果该基类还有其自身的基类,那么第二个基类就会被加载,如此类推。
  2. 接下来,根基类的static初始化会被执行,然后是下一个导出类,如此类推。
  3. 必要的类加载完成后,对象就可以被创建。同样的,首先对象中所有的基本类型都会被设为默认值,对象引用被设为null——通过将对象内存设为二进制零值而一举生成。
  4. 然后基类的构造器会被调用。基类构造器和导出类的构造器一样,以相同的顺序来经历相同的过程。
  5. 在基类构造器完成之后,实例变量按其次序被初始化。
  6. 最后,构造器的其余部分被执行。

在有了上述归纳后,我们来分析上面程序的结果。

  • 在类Son中执行main方法,由于main()是静态方法,必须加载Son类,在加载的时候发现其有父类Father,进而加载Father类。
  • Father类被加载的时候,其静态变量staticVarInFather被初始化,之后Son类中的静态变量staticVarInSon被初始化。
  • 回到main方法,打印出“start running”
  • 在执行new Son("Bob")的时候,对基类也就是Father中的varInFather进行初始化,之后Father的构造器被调用。
  • 之后导出类变量varInSon被初始化,调用导出类Son的构造器。

具有继承的静态内部类

关于这个的讲解,我引用一道2015携程Java工程师笔试题。来自csdb博客fuck两点水 2015携程JAVA工程师笔试题(基础却又没多少人做对的面向对象面试题)。题目如下:

public class Base
{
    private String baseName = "base";
    public Base()
    {
        callName();
    }

    public void callName()
    {
        System. out. println(baseName);
    }

    static class Sub extends Base
    {
        private String baseName = "sub";
        public void callName()
        {
            System. out. println (baseName) ;
        }
    }
    public static void main(String[] args)
    {
        Base b = new Sub();
    }
}

当时看到这道题的时候,关于类的加载,初始化基本已经忘记,所以直接做错。该题的正确答案是:

null

为什么是null?首先我们从上面的内容可以了解到,类的初始化顺序是:

父类静态块 ->子类静态块 ->父类初始化语句 ->父类构造函器 ->子类初始化语句 子类构造器。

其实在掌握了我上面说的东西后,这道题的的答案为什么为null,已经是“柳暗花明又一村了”;所以我这里直接把fuck两点水博客上的内容摘抄过来

  1. Base b = new Sub();在 main方法中声明父类变量b对子类的引用,JAVA类加载器将Base,Sub类加载到JVM;也就是完成了 Base 类和 Sub 类的初始化
  2. JVM 为 Base,Sub 的的成员开辟内存空间且值均为null;在初始化Sub对象前,首先JAVA虚拟机就在堆区开辟内存并将子类 Sub 中的 baseName 和父类 Base 中的 baseName(已被隐藏)均赋为 null,就是子类继承父类的时候,同名的属性不会覆盖父类,只是会将父类的同名属性隐藏
  3. 调用父类的无参构造调用 Sub 的构造函数,因为子类没有重写构造函数,默认调用无参的构造函数,调用了 super() 。
  4. callName 在子类中被重写,因此调用子类的 callName();调用了父类的构造函数,父类的构造函数中调用了 callName 方法,此时父类中的 baseName 的值为 base,可是子类重写了 callName 方法,且 调用父类 Base 中的 callName 是在子类 Sub 中调用的,因此当前的 this 指向的是子类,也就是说是实现子类的 callName 方法
  5. 调用子类的callName,打印baseName

实际上在new Sub()时,实际执行过程为:

public Sub(){
    super();
    baseName = "sub"; 
}

可见,在 baseName = “sub” 执行前,子类的 callName() 已经执行,所以子类的 baseName 为默认值状态 null 。
  上面的题,大家可以试着把子类中的baseName使用static进行修饰,看看会得到什么结果,加深自己的理解。
  关于类的加载和初始化的备忘录就到此结束了。

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

推荐阅读更多精彩内容

  • 这票文章主要想来说一下Java类各部分(非静态字段初始化、非静态块、静态字段初始化、静态块、构造函数)的执行顺序。...
    第四单元阅读 421评论 0 2
  • 今天介绍下java类的初始化顺序。 这里写了个类方便更直观的表达(代码太长没人看) 运行结果 从结果我们可以看出 ...
    rainumdo阅读 369评论 0 1
  • 父类 static 块 1 执行 父类 静态成员staticSam1初始化 父类 静态成员staticSam2初始...
    YCix阅读 1,305评论 0 0
  • 20- 枚举,枚举原始值,枚举相关值,switch提取枚举关联值 Swift枚举: Swift中的枚举比OC中的枚...
    iOS_恒仔阅读 2,256评论 1 6
  • 之前从来没练过这种题。。。 严重超时。。。。 我特别喜欢这个方法,比较全面的展示了对Binary的理解。可以发现,...
    98Future阅读 381评论 0 0