九、 Java程式初始化的順序(二)

renke發表於2021-09-09
之前的一篇部落格裡我寫了關於在一個類中的程式初始化順序,但是在Java的物件導向裡,類之間還存在著繼承的關係。所以關於程式的初始化順序,我們可以再細劃分為:父類靜態變數,父類的靜態程式碼塊,父類構造器,父類非靜態變數,父類非靜態程式碼塊,子類靜態變數,子類靜態程式碼塊,子類構造器,子類非靜態成員變數和子類非靜態程式碼塊。
本篇部落格我們討論的就是關於程式初始化的過程中,上述的成員在初始化載入先後順序。

  在此前我們討論得出的結論:在一個類中,Java程式載入的順序是:靜態變數-->靜態程式碼塊-->非靜態變數-->非靜態程式碼塊-->構造器.

  父類的程式碼:

圖片描述

public class SuperClass {    //父類與子類都在一個包中,這裡我們就使用default修飾符    //這是一個父類的靜態變數,此時還是初始化的預設值null
    static String superStaticVariale;    // 靜態程式碼塊,給String賦值
    static {
        superStaticVariale = "父類靜態程式碼塊賦值成功";
        System.out.println("此時執行的是父類的靜態程式碼塊:"+superStaticVariale);
    }    // 無參構造,覆蓋靜態程式碼塊的值    SuperClass(){
        superStaticVariale = "父類構造器賦值成功";
        System.out.println("此時執行的是父類的構造器:"+superStaticVariale);
    }    //定義一個非靜態變數    String superVariale;    // 定義一個非靜態程式碼塊    {
        superVariale = "父類非靜態程式碼塊賦值";
        System.out.println("此時執行的是父類的非靜態程式碼塊:"+superVariale);
    }
}

圖片描述

  子類的程式碼:

圖片描述

public class SubClass extends SuperClass{    static String subStaticVariale;    // 靜態程式碼塊,給String賦值
    static {
        subStaticVariale = "子類靜態程式碼塊賦值成功";
        System.out.println("此時執行的是子類的靜態程式碼塊:"+subStaticVariale);
    }    // 無參構造,覆蓋靜態程式碼塊的值    SubClass(){
        superStaticVariale = "子類構造器賦值成功";
        System.out.println("此時執行的是子類的構造器:"+superStaticVariale);
    }    //定義一個非靜態變數    String subVariale;    // 定義一個非靜態程式碼塊    {
        subVariale = "子類非靜態程式碼塊賦值";
        System.out.println("此時執行的是子類非靜態程式碼塊:"+subVariale);
    }
}

圖片描述

  測試程式碼:
public class Main {    public static void main(String[] args) {
        SubClass s = new SubClass();
    }
}
  執行結果:
```
此時執行的是父類的靜態程式碼塊:父類靜態程式碼塊賦值成功
此時執行的是子類的靜態程式碼塊:子類靜態程式碼塊賦值成功
此時執行的是父類的非靜態程式碼塊:父類非靜態程式碼塊賦值
此時執行的是父類的構造器:父類構造器賦值成功
此時執行的是子類非靜態程式碼塊:子類非靜態程式碼塊賦值
此時執行的是子類的構造器:子類構造器賦值成功
```
  很顯然,在繼承關係中,程式碼的載入順序是:父類的靜態變數-->父類的靜態程式碼塊-->子類靜態變數-->子類的靜態程式碼塊-->父類非靜態變數-->父類的非靜態程式碼塊-->父類的構造器-->子類非靜態變數-->子類非靜態程式碼塊-->子類構造器

  進一步測試:

圖片描述

public class Main {    public static void main(String[] args) {
        SubClass s = new SubClass();
        SubClass s1 = new SubClass();
        SubClass s2 = new SubClass();
    }
}

圖片描述

執行結果:
```
此時執行的是父類的靜態程式碼塊:父類靜態程式碼塊賦值成功
此時執行的是子類的靜態程式碼塊:子類靜態程式碼塊賦值成功
此時執行的是父類的非靜態程式碼塊:父類非靜態程式碼塊賦值
此時執行的是父類的構造器:父類構造器賦值成功
此時執行的是子類非靜態程式碼塊:子類非靜態程式碼塊賦值
此時執行的是子類的構造器:子類構造器賦值成功
此時執行的是父類的非靜態程式碼塊:父類非靜態程式碼塊賦值
此時執行的是父類的構造器:父類構造器賦值成功
此時執行的是子類非靜態程式碼塊:子類非靜態程式碼塊賦值
此時執行的是子類的構造器:子類構造器賦值成功
此時執行的是父類的非靜態程式碼塊:父類非靜態程式碼塊賦值
此時執行的是父類的構造器:父類構造器賦值成功
此時執行的是子類非靜態程式碼塊:子類非靜態程式碼塊賦值
此時執行的是子類的構造器:子類構造器賦值成功
```
  得出結論:
  父類與子類的靜態程式碼都只執行一次,然後非靜態程式碼塊與構造器是組合出現的。

  簡化一下程式碼:

圖片描述

public class Main {    public static void main(String[] args) {
   C c= new C();
    }
}class A{
    A(){
        System.out.println("A的無參構造器");
    }
}class B extends A{//    B(int a){    B(){
        System.out.println("B的無參構造器");
    }
}class C extends B{
    C(){
        System.out.println("C的無參構造器");
    }
}

圖片描述

  執行結果:
```text
A的無參構造器
B的無參構造器
C的無參構造器
```
  呼叫C的構造器生成C的例項物件會從最上級的父類的無參構造器開始逐層呼叫,那麼我們的類都繼承了一個超級父類Object,也就是在我們最初的錯誤程式碼中,我們呼叫Student的無參構造建立一個物件時,首先會呼叫這個物件的父類Object的無參構造器,

圖片描述

class Student{
   String name;
   
   {
      name = "老大";
   }
   
   Student(){       this(name);//這樣會報錯
      super();
      System.out.println("題目要求寫一個無參的構造器");
   }
   
   Student(String name){      this.name = name;
      System.out.println(name);
   }
   
}

圖片描述

  子類例項化預設呼叫父類的無參構造器,也就是如上this呼叫在super()之前(實際中這兩者不會同時出現),name此時是非靜態屬性,此時會報錯錯誤: 無法在呼叫超型別構造器之前引用name。

圖片描述

class Student{   static String name;
   
   {
      name = "老大";
   }
   
   Student(){       this(name);
      System.out.println("題目要求寫一個無參的構造器");
   }
   
   Student(String name){      this.name = name;
      System.out.println(name);
   }
   
}

圖片描述

  當name是靜態屬性時,程式碼塊是非靜態時,編譯透過,呼叫子類的無參構造器時this(name),輸出結果是:
```text
null
題目要求寫一個無參的構造器
```
此時的this()呼叫實參構造並沒有賦值成功。

圖片描述

class Student{   static String name;   
   static{
      name = "老大";
   }
   
   Student(){       this(name);
      System.out.println("題目要求寫一個無參的構造器");
   }
   
   Student(String name){      this.name = name;
      System.out.println(name);
   }
}

圖片描述

  此時執行結果:
```text
老大
題目要求寫一個無參的構造器
```
  這樣賦值成功。由此證明我們的結論是正確的,this()是在子類父類構造器之前進行的操作super(),當子類程式碼塊是非靜態時,子類非靜態程式碼塊會在執行父類構造器之後執行,所以this(name)時name還沒有被賦值,所以列印是null。

  結論:
  1. 一個類中可以在無參構造器中呼叫此類的有參構造器(順序反過來);
  2. 在執行子類的無參構造器時會預設呼叫最高階父類無參構造,並逐級呼叫直至子類的無參構造;
  3. Java程式的載入順為父類的靜態變數-->父類的靜態程式碼塊-->子類靜態變數-->子類的靜態程式碼塊-->父類非靜態變數-->父類的非靜態程式碼塊-->父類的構造器-->子類非靜態變數-->子類非靜態程式碼塊-->子類構造器,且靜態變數或程式碼塊無論構造器呼叫多少次,他只會執行一次,後面再呼叫構造器則會執行非靜態屬性及程式碼塊構造器。

  最後關於為什麼子類會呼叫父類的構造器,這個從設計著的角度來看是為了給從父類繼承的屬性初始化,子類需要知道父類是如何給屬性初始化的。

原文出處:https://www.cnblogs.com/Jeffding/p/9490510.html

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/430/viewspace-2812227/,如需轉載,請註明出處,否則將追究法律責任。

相關文章