Java類的初始化順序

title picture
title picture

??最近在看回顧Java基礎(chǔ)的時候,發(fā)現(xiàn)看似很簡單的類初始化的順序卻并不是那么簡單(往往越是簡單的東西反而越容易出錯呢),所以我覺得還是把它寫下來,作為自己的備忘錄比較好。既然都記錄了我覺得我還是記錄得比較全面的較好,所以顯得有點啰嗦。

普通類的初始化(不存在繼承,內(nèi)部類的時候)

為了更詳細(xì)的驗證類的初始化順序,首先我創(chuàng)建了一個被另一個類使用的類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
    
    /**
     * 構(gòu)造方法
     */
    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;
    }
}

然后我創(chuàng)建了一個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
    
    /**
     * 構(gòu)造方法
     */
    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
    }
}

運行后結(jié)果為:

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》這本書里面的關(guān)于初始化順序的總結(jié)進(jìn)行歸納如下:

注意:即使變量定義散布于方法定義之間,它們?nèi)耘f會在任何方法(包括構(gòu)造器)被調(diào)用之前得到初始化。

  1. 即使沒有顯示地使用static關(guān)鍵字,構(gòu)造器實際上也是靜態(tài)方法。因此,當(dāng)首次創(chuàng)建類的對象時(構(gòu)造器可以看出靜態(tài)方法),或者類的靜態(tài)方法/靜態(tài)域被首次訪問時,Java解釋器必須查找類路徑。
  2. 然后載入class,有關(guān)靜態(tài)初始化的所有動作都會執(zhí)行(所以靜態(tài)初始化只在Class對象首次被加載的時候進(jìn)行一次)。
  3. 當(dāng)使用new創(chuàng)建對象的時候,首先將在堆上為對象分配足夠的存儲空間。
  4. 這塊存儲空間會被清零,這就自動將Dog對象中的所有基本類型數(shù)據(jù)都設(shè)置成了默認(rèn)值(對數(shù)字來說就是0,對布爾類型和字符類型也相同),而引用就則被設(shè)置成了null。
  5. 執(zhí)行出現(xiàn)于字段定義出的初始化動作。
  6. 執(zhí)行構(gòu)造器。

有了上面的知識點,再來看上面的結(jié)果。我用數(shù)字1 2 3做了標(biāo)記,括號后的阿拉伯?dāng)?shù)字表示上面代碼對應(yīng)的地方。

  • 在類A中執(zhí)行main方法,由于main()是靜態(tài)方法,必須加載A類,然后起靜態(tài)域staticVarOneInA(1),staticvarTwoInA(2),staticB(3)被初始化。
  • 在staticB被初始化的時候,導(dǎo)致B類被加載,因為是第一次加載,對靜態(tài)域進(jìn)行初始化,因此staticVarOneInB(4),staticvarTwoInB(5)被初始化。
  • 之后順序初始化varOneInB(6),varTwoInB(7),執(zhí)行構(gòu)造器B(8)。
  • 在A靜態(tài)域初始化后,回到main()方法,打印出了“start running”(9),在new A()(10)的時候,分配a對象的空間,開始順序初始化varOneInA(11),varTwoInA(12)和b(13),初始化b的時候因為不是第一次加載,所以staticVarOneInB,staticvarTwoInB不再被初始化,只是初始化了varOneInB(14),varTwoInB(15),然后執(zhí)行構(gòu)造B(16)。
  • 初始化完成后,調(diào)用A的構(gòu)造器(17);最后通過a.run()調(diào)用run(18)方法,打印出“run be called”。

具有繼承的類的初始化

下面,我創(chuàng)建了一個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");
    }
}

輸出結(jié)果如下;

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

同樣的,我將《Think in java》中的關(guān)于繼承的類加載和初始化歸納如下:

注意:即使變量定義散布于方法定義之間,它們?nèi)耘f會在任何方法(包括構(gòu)造器)被調(diào)用之前得到初始化。

  1. (同上)即使沒有顯示地使用static關(guān)鍵字,構(gòu)造器實際上也是靜態(tài)方法。因此,當(dāng)首次創(chuàng)建類的對象時(構(gòu)造器可以看出靜態(tài)方法),或者類的靜態(tài)方法/靜態(tài)域被首次訪問時,Java解釋器必須查找類路徑,在對它進(jìn)行加載的過程中,編譯器注意到它有一個基類(有extends得知),于是它繼續(xù)加載,不管你時候打算產(chǎn)生一個該基類的對象。如果該基類還有其自身的基類,那么第二個基類就會被加載,如此類推。
  2. 接下來,根基類的static初始化會被執(zhí)行,然后是下一個導(dǎo)出類,如此類推。
  3. 必要的類加載完成后,對象就可以被創(chuàng)建。同樣的,首先對象中所有的基本類型都會被設(shè)為默認(rèn)值,對象引用被設(shè)為null——通過將對象內(nèi)存設(shè)為二進(jìn)制零值而一舉生成。
  4. 然后基類的構(gòu)造器會被調(diào)用。基類構(gòu)造器和導(dǎo)出類的構(gòu)造器一樣,以相同的順序來經(jīng)歷相同的過程。
  5. 在基類構(gòu)造器完成之后,實例變量按其次序被初始化。
  6. 最后,構(gòu)造器的其余部分被執(zhí)行。

在有了上述歸納后,我們來分析上面程序的結(jié)果。

  • 在類Son中執(zhí)行main方法,由于main()是靜態(tài)方法,必須加載Son類,在加載的時候發(fā)現(xiàn)其有父類Father,進(jìn)而加載Father類。
  • Father類被加載的時候,其靜態(tài)變量staticVarInFather被初始化,之后Son類中的靜態(tài)變量staticVarInSon被初始化。
  • 回到main方法,打印出“start running”
  • 在執(zhí)行new Son("Bob")的時候,對基類也就是Father中的varInFather進(jìn)行初始化,之后Father的構(gòu)造器被調(diào)用。
  • 之后導(dǎo)出類變量varInSon被初始化,調(diào)用導(dǎo)出類Son的構(gòu)造器。

具有繼承的靜態(tài)內(nèi)部類

關(guān)于這個的講解,我引用一道2015攜程Java工程師筆試題。來自csdb博客fuck兩點水 2015攜程JAVA工程師筆試題(基礎(chǔ)卻又沒多少人做對的面向?qū)ο竺嬖囶})。題目如下:

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();
    }
}

當(dāng)時看到這道題的時候,關(guān)于類的加載,初始化基本已經(jīng)忘記,所以直接做錯。該題的正確答案是:

null

為什么是null?首先我們從上面的內(nèi)容可以了解到,類的初始化順序是:

父類靜態(tài)塊 ->子類靜態(tài)塊 ->父類初始化語句 ->父類構(gòu)造函器 ->子類初始化語句 子類構(gòu)造器。

其實在掌握了我上面說的東西后,這道題的的答案為什么為null,已經(jīng)是“柳暗花明又一村了”;所以我這里直接把fuck兩點水博客上的內(nèi)容摘抄過來

  1. Base b = new Sub();在 main方法中聲明父類變量b對子類的引用,JAVA類加載器將Base,Sub類加載到JVM;也就是完成了 Base 類和 Sub 類的初始化
  2. JVM 為 Base,Sub 的的成員開辟內(nèi)存空間且值均為null;在初始化Sub對象前,首先JAVA虛擬機就在堆區(qū)開辟內(nèi)存并將子類 Sub 中的 baseName 和父類 Base 中的 baseName(已被隱藏)均賦為 null,就是子類繼承父類的時候,同名的屬性不會覆蓋父類,只是會將父類的同名屬性隱藏
  3. 調(diào)用父類的無參構(gòu)造調(diào)用 Sub 的構(gòu)造函數(shù),因為子類沒有重寫構(gòu)造函數(shù),默認(rèn)調(diào)用無參的構(gòu)造函數(shù),調(diào)用了 super() 。
  4. callName 在子類中被重寫,因此調(diào)用子類的 callName();調(diào)用了父類的構(gòu)造函數(shù),父類的構(gòu)造函數(shù)中調(diào)用了 callName 方法,此時父類中的 baseName 的值為 base,可是子類重寫了 callName 方法,且 調(diào)用父類 Base 中的 callName 是在子類 Sub 中調(diào)用的,因此當(dāng)前的 this 指向的是子類,也就是說是實現(xiàn)子類的 callName 方法
  5. 調(diào)用子類的callName,打印baseName

實際上在new Sub()時,實際執(zhí)行過程為:

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

可見,在 baseName = “sub” 執(zhí)行前,子類的 callName() 已經(jīng)執(zhí)行,所以子類的 baseName 為默認(rèn)值狀態(tài) null 。
??上面的題,大家可以試著把子類中的baseName使用static進(jìn)行修飾,看看會得到什么結(jié)果,加深自己的理解。
??關(guān)于類的加載和初始化的備忘錄就到此結(jié)束了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 這票文章主要想來說一下Java類各部分(非靜態(tài)字段初始化、非靜態(tài)塊、靜態(tài)字段初始化、靜態(tài)塊、構(gòu)造函數(shù))的執(zhí)行順序。...
    第四單元閱讀 451評論 0 2
  • 今天介紹下java類的初始化順序。 這里寫了個類方便更直觀的表達(dá)(代碼太長沒人看) 運行結(jié)果 從結(jié)果我們可以看出 ...
    rainumdo閱讀 441評論 0 1
  • 父類 static 塊 1 執(zhí)行 父類 靜態(tài)成員staticSam1初始化 父類 靜態(tài)成員staticSam2初始...
    YCix閱讀 1,399評論 0 0
  • 20- 枚舉,枚舉原始值,枚舉相關(guān)值,switch提取枚舉關(guān)聯(lián)值 Swift枚舉: Swift中的枚舉比OC中的枚...
    iOS_恒仔閱讀 2,423評論 1 6
  • 之前從來沒練過這種題。。。 嚴(yán)重超時。。。。 我特別喜歡這個方法,比較全面的展示了對Binary的理解??梢园l(fā)現(xiàn),...
    98Future閱讀 464評論 0 0

友情鏈接更多精彩內(nèi)容