Java對象初始化過程代碼塊和構造器的調用順序

前言

 對Java對象初始化過程 代碼塊與構造器調用順序進行整理說明。先說結論具體論證在下文。

代碼加載的優先級順序 

靜態代碼塊、靜態成員變量->非靜態代碼塊、非靜態成員變量->new其他對象調用對應對象構造方法(在本地對象的方法外包括構造方法)->new本地對象調用構造方法。 

註意:若new對象時,該對象中有靜態代碼塊和非靜態代碼塊,每new一次對象,非靜態代碼塊都會執行一次,但靜態代碼塊隻會執行一次往後new對象都不會再執行。】 

 構造方法的執行順序 

父類靜態代碼塊(靜態變量 > 靜態塊) > 子類的靜態代碼塊 > 父類構造代碼塊、構造方法> 子類的構造代碼塊、構造方法 

各種代碼塊的定義 

 靜態代碼塊 

class Demo{ static { //靜態代碼塊...... } }

特點:  1、Java靜態代碼塊中的代碼會在類加載JVM時運行,且隻被執行一次 2、靜態塊常用來執行類屬性的初始化 ,和一些全局初始化的工作 3、靜態塊優先於各種代碼塊以及構造函數,如果一個類中有多個靜態代碼塊,會按照書寫順序依次執行 4、靜態代碼塊可以定義在類的任何地方中除瞭方法體中【這裡的方法體是任何方法體】 5、靜態代碼塊不能訪問普通變量

有關靜態代碼塊再詳細介紹下

靜態代碼塊:在java中使用static關鍵字聲明的代碼塊。靜態塊用於初始化類,為類的屬性初始化。每個靜態代碼塊隻會執行一次。

由於JVM在加載類時會執行靜態代碼塊,所以靜態代碼塊先於主方法執行。 如果類中包含多個靜態代碼塊,那麼將按照"先定義的代碼先執行,後定義的代碼後執行"。 【註意:1 靜態代碼塊不能存在於任何方法體內。2 靜態代碼塊不能直接訪問靜態實例變量和實例方法,需要通過類的實例對象來訪問。】 實例代碼塊 實例代碼塊 又叫 構造初始化塊 , 構造代碼塊 , 初始化塊 。

class Demo{ { //實例代碼塊...... } }

特點:

  • 1、構造代碼塊在創建對象時被調用,每次創建對象都會調用一次
  • 2、構造代碼塊優先於構造函數執行,同時構造代碼塊的運行依賴於構造函數
  • 3、構造代碼塊在類中定義

局部代碼塊

局部代碼塊又叫 普通代碼塊 , 方法代碼塊

class Demo{ public void test(){ { //局部代碼塊...... } } }

特點: 1、普通代碼塊定義在方法體中 2、普通代碼塊與實例代碼塊的格式一致都是{} 3、普通代碼塊與構造代碼塊唯一能直接看出的區別是構造代碼塊是在類中定義的,而普通代碼塊是在方法體中定義的 4、可以限定變量生命周期,及早釋放,提高內存利用率

驗證各代碼塊的執行順序

舉例代碼如下:

class Init {
    public Init() {
        System.out.println("無參構造器");
    }
    public Init(int a) {
        System.out.println("有參構造器");
    }
    {
        System.out.println("實例代碼塊1");
    }
    {
        System.out.println("實例代碼塊2");
    }
    {
        System.out.println("實例代碼塊3");
    }
    static {
        System.out.println("靜態初始化塊1");
    }
 
    static {
        System.out.println("靜態初始化塊2");
    }

    public void method(){
    	{
    		System.out.println("普通初始化塊");
    	}
    }
}

測試代碼 如下:

class Demo {
    public static void main(String[] args) {
        Init init1 = new Init();
        init1.method();
        System.out.println("------------");
        Init init2 = new Init();
        init2.method();
        //多打印幾個對象的目的是:方便看出Static靜態代碼塊 是否隻執行一次!!!
        System.out.println("------------");
        Init init3 = new Init();
        init3.method();
    }
}

運行結果如下圖:

結論:

執行順序為:靜態代碼塊 > 實例代碼塊 > 構造函數 > 普通代碼塊,

且靜態代碼塊,類加載的時候就會調用,且隻調用一次(隨著類的加載而執行)。

那麼類什麼時候會被加載呢?

– 創建對象實例時(new)
– 創建子類對象實例,父類也會被加載
– 使用類的靜態成員時(靜態屬性,靜態方法)

驗證存在繼承關系中各代碼塊的執行順序

舉例繼承關系為 Three——> Two——> One,

代碼如下:

class One {
    public One() {
        System.out.println("One構造器");
    }
 
    {
        System.out.println("One實例化塊");
    }
 
    static {
        System.out.println("One靜態代碼塊");
 
    }
 
}
class Two extends One {
 
    public Two() {
        System.out.println("Two構造器");
    }
 
    {
        System.out.println("Two實例化塊");
    }
 
    static {
        System.out.println("Two靜態代碼塊");
    }
 
}
 
class Three extends Two {
 
    public Three() {
        System.out.println("Three構造器");
    }
 
    {
        System.out.println("Three實例化塊");
    }
    static {
        System.out.println("Three靜態代碼塊");
    }
 
}
//測試代碼 如下:
public class Demo {
    public static void main(String[] args) {
        Three three = new Three();
        System.out.println("-----");
        Three three1 = new Three(); //重復執行的目的是為瞭 驗證static是否隻執行一次
        System.out.println("-----");
        Two three2 = new Three();   //驗證 多態的情況下 用後面的類進行初始化 結果和上面一樣
    }
}

根據執行結果可知,在多個類的繼承中存在初始化塊、靜態初始化塊、構造器,執行真實順序為:先後執行父類A的靜態塊,父類B的靜態塊,最後子類的靜態塊,然後再執行父類A的實例代碼塊和構造器,然後是B類的實例代碼塊和構造器,最後執行子類C的實例代碼塊和構造器【註:這裡的ABC對應One、Two、Three 】

結論:

多個類的繼承中初始化塊、靜態初始化塊、構造器的執行順序為:

父類靜態塊——>子類靜態塊——>父類實例代碼塊——>父類構造器——>子類實例代碼塊——>子類構造器 ——>(如果有局部代碼塊, 再正常執行即可, 這裡就沒必要進行測試瞭)

通過字節碼深究實例代碼塊優先於構造器原因

我們那一段代碼作為例子說明下,代碼如下:

class Init {
    public Init() {
        System.out.println("無參構造器");
    }
    public Init(int a) {
        System.out.println("有參構造器");
 
    }
 
    {
        System.out.println("實例代碼塊1");
    }
 
    {
        System.out.println("實例代碼塊2");
    }
 
    {
        System.out.println("實例代碼塊3");
    }
 
    static {
        System.out.println("靜態初始化塊1");
    }
 
    static {
        System.out.println("靜態初始化塊2");
    }
 
    public void method(){
        {
            System.out.println("普通初始化塊");
        }
    }
}

接下來讓我們看看 , Init.java編譯完的的字節碼文件(Init.class)

從這個字節碼文件就可以很清晰的看出, 實例代碼塊實際上是被依次放到瞭構造方法的第一句, 所以可以的出此結論: 實例代碼塊的執行順序是優先於構造器的。

到此這篇關於Java對象初始化過程代碼塊和構造器的調用順序的文章就介紹到這瞭,更多相關Java對象初始化過內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: