之前的一篇博客里我寫了關於在一個類中的程式初始化順序,但是在Java的面向對象里,類之間還存在著繼承的關係。所以關於程式的初始化順序,我們可以再細劃分為:父類靜態變數,父類的靜態代碼塊,父類構造器,父類非靜態變數,父類非靜態代碼塊,子類靜態變數,子類靜態代碼塊,子類構造器,子類非靜態成員變數和子類非 ...
之前的一篇博客里我寫了關於在一個類中的程式初始化順序,但是在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程式的載入順為父類的靜態變數-->父類的靜態代碼塊-->子類靜態變數-->子類的靜態代碼塊-->父類非靜態變數-->父類的非靜態代碼塊-->父類的構造器-->子類非靜態變數-->子類非靜態代碼塊-->子類構造器,且靜態變數或代碼塊無論構造器調用多少次,他只會執行一次,後面再調用構造器則會執行非靜態屬性及代碼塊構造器。
最後關於為什麼子類會調用父類的構造器,這個從設計著的角度來看是為了給從父類繼承的屬性初始化,子類需要知道父類是如何給屬性初始化的。