【java基礎☞初始化順序】java繼承中的初始化順序

一、初始化順序:父類的靜態變量-->父類的靜態代碼塊-->子類的靜態變量-->子類的靜態代碼快-->父類的非靜態變量(父類的非靜態代碼塊)-->父類的構造函數-->子類的非靜態變量(子類的非靜態代碼塊)-->子類的構造函數java

重點關注:父類的非靜態變量(父類的非靜態代碼塊)是同一級的,看哪一個部分寫在類的最前面誰就先被執行,子類的非靜態變量(子類的非靜態代碼塊)也是同樣。

package com.test;


public class Test {
    public static void main(String[] args) {
        Child c=new Child();
    }
}
class Parent {
    public static PrintMessage a=new PrintMessage("父類靜態成員被初始化");
    private PrintMessage b=new PrintMessage("父類非靜態成員被初始化"); 
    static{
        System.out.println("父類的靜態代碼塊被執行");
    }
    {
        System.out.println("父類的非靜態代碼塊被執行");
    }
    public Parent(){
        System.out.println("父類的構造方法被執行");
    }   
}

class Child extends Parent{
    public static PrintMessage a1=new PrintMessage("子類靜態成員被初始化");
    private PrintMessage b1=new PrintMessage("子類非靜態成員被初始化");    
    
    static {
        System.out.println("子類的靜態代碼塊被執行");
    }
    {
        System.out.println("子類的非靜態代碼塊被執行");
    }
    public Child(){
        System.out.println("子類的構造函數被執行");
    }
}

class PrintMessage{
    public PrintMessage(String mes){
        System.out.println(mes);
    }
}

執行結果:數據結構

父類靜態成員被初始化
父類的靜態代碼塊被執行
子類靜態成員被初始化
子類的靜態代碼塊被執行
父類非靜態成員被初始化
父類的非靜態代碼塊被執行
父類的構造方法被執行
子類非靜態成員被初始化
子類的非靜態代碼塊被執行
子類的構造函數被執行函數

 

二、JAVA 類的加載機制spa

Java類加載分爲5個過程,分別爲:加載,鏈接(驗證,準備,解析),初始化,使用,卸載。設計

1. 加載
    加載主要是將.class文件(也能夠是zip包)經過二進制字節流讀入到JVM中。 在加載階段,JVM須要完成3件事:
    1)經過classloader在classpath中獲取XXX.class文件,將其以二進制流的形式讀入內存。
    2)將字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構;
    3)在內存中生成一個該類的java.lang.Class對象,做爲方法區這個類的各類數據的訪問入口。code

2.鏈接對象

    2.1. 驗證
    主要確保加載進來的字節流符合JVM規範。驗證階段會完成如下4個階段的檢驗動做:
    1)文件格式驗證
    2)元數據驗證(是否符合Java語言規範)
    3)字節碼驗證(肯定程序語義合法,符合邏輯)
    4)符號引用驗證(確保下一步的解析能正常執行)
    2.2. 準備
    準備是鏈接階段的第二步,主要爲靜態變量在方法區分配內存,並設置默認初始值。
    2.3. 解析
    解析是鏈接階段的第三步,是虛擬機將常量池內的符號引用替換爲直接引用的過程。繼承

3. 初始化ip

    初始化階段是類加載過程的最後一步,主要是根據程序中的賦值語句主動爲類變量賦值。
    當有繼承關係時,先初始化父類再初始化子類,因此建立一個子類時其實內存中存在兩個對象實例。
 注:若是類的繼承關係過長,單從類初始化角度考慮,這種設計不太可取。緣由我想你已經猜到了。
    一般建議的類繼承關係最多不超過三層,即父-子-孫。某些特殊的應用場景中可能會加到4層,但就此打住,第4層已經有代碼設計上的弊端了。
內存

4. 使用
    程序之間的相互調用。

5. 卸載
    即銷燬一個對象,通常狀況下中有JVM垃圾回收器完成。代碼層面的銷燬只是將引用置爲null。

經過上面的總體介紹後,再來看Singleton2.getInstance()的執行分析:
    1)類的加載。運行Singleton2.getInstance(),JVM在首次並無發現Singleton類的相關信息。因此經過classloader將Singleton.class文件加載到內存中。
    2)類的驗證。略
    3)類的準備。將Singleton2中的靜態資源轉化到方法區。value1,value2,singleton在方法區被聲明分別初始爲0,0,null。
    4)類的解析。略(將常量池內的符號引用替換爲直接引用的過程)
    5)類的初始化。執行靜態屬性的賦值操做。按照順序先是value1 = 5,value2 = 3,接下來是private static Singleton2 singleton2 = new Singleton2();
這是個建立對象操做,根據 結論1 在執行Singleton2的構造方法以前,先去執行static資源和非static資源。但因爲value1,value2已經被初始化過,因此接下來執行的是非static的資源,最後是Singleton2的構造方法:value1++;value2++。
因此Singleton2結果是6和4。

以上除了搞清楚執行順序外,還有一個重點->結論2:靜態資源在類的初始化中只會執行一次。不要與第3個步驟混淆。

有了以上的這個結論,再來看Singleton.getInstance()的執行分析:
    1)類的加載。將Singleton類加載到內存中。
    2)類的驗證。略
    3)類的準備。將Singleton2的靜態資源轉化到方法區。
    4)類的解析。略(將常量池內的符號引用替換爲直接引用的過程)
    5)類的初始化。執行靜態屬性的賦值操做。按照順序先是private static Singleton singleton = new Singleton(),根據 結論1 和結論2,value1和value2不會在此層執行賦值操做。因此singleton對象中的value1,value2只是在0的基礎上進行了++操做。此時singleton對象中的value1=1,value2=1。 而後, public static int value1 = 5; public static int value2 = 3; 這兩行代碼纔是真的執行了賦值操做。因此最後的結果:5和3。 若是執行的是public static int value1; public static int value2;結果又會是多少?結果: 1和1。

相關文章
相關標籤/搜索