由於Java 中的一切東西都是物件,所以許多活動 變得更加簡單,這個問題便是其中的一例。
除非真的需要程式碼,否則那個檔案是不會載入的。通常,我們可認為除非那個類的一個物件構造完畢, 否則程式碼不會真的載入。由於static 方法存在一些細微的歧義,所以也能認為“類程式碼在首次使用的時候載入”。 首次使用的地方也是static 初始化發生的地方。裝載的時候,所有static 物件和static 程式碼塊都會按照本 來的順序初始化(亦即它們在類定義程式碼裡寫入的順序)。當然,static 資料只會初始化一次。
簡要的說就是,在類有繼承關係時,類載入器上溯造型,進行相關類的載入工作。
比如:
Class B extends Class A
當我們new B()時,類載入器自動載入A的程式碼
class的初始化順序
通常是以下這樣的初始化順序:
(static物件和static程式碼塊,依據他們的順序進行初始化)->成員變數和程式碼塊(依據他們的順序進行初始化)->建構函式
例如:
package cn.d; public class ClassInit { public static void main(String[] args) { new B(); System.out.println("------------"); new B(); } } class A { static { System.out.println("A的static程式碼塊...");// 1 } { System.out.println("A的程式碼塊...");// 1 } public String s1 = prtString("A的成員變數..."); public static String s2 = prtString("A的static變數...");// 2 public A() { System.out.println("A的建構函式..."); } public static String prtString(String str) { System.out.println(str); return null; } } class B extends A { public String ss1 = prtString("B的成員變數..."); { System.out.println("B的程式碼塊..."); } public static String ss2 = prtString("B的static變數...");// 3. public B() { System.out.println("B的建構函式..."); } private static A a = new A();// 4. static { System.out.println("B的static程式碼塊..."); } }
結果:
A的static程式碼塊...
A的static變數...
B的static變數...
A的程式碼塊...
A的成員變數...
A的建構函式...
B的static程式碼塊...
A的程式碼塊...
A的成員變數...
A的建構函式...
B的成員變數...
B的程式碼塊...
B的建構函式...
------------
A的程式碼塊...
A的成員變數...
A的建構函式...
B的成員變數...
B的程式碼塊...
B的建構函式...
解釋:
1. 首先載入A的靜態程式碼快和靜態變數,由於A中靜態程式碼塊刈寫在前面,因此先載入靜態程式碼塊後載入靜態變數。
2. 然後載入B的靜態程式碼快和靜態成員變數,由於B中靜態變數在前面所以先載入B的靜態變數,當執行到 private static A a = new A();的時候會先載入A的成員變數再執行A的建構函式。最後執行B的靜態程式碼塊
3. 接下來載入成員變數、程式碼塊和建構函式,先載入父類的成員變數、程式碼塊和建構函式,然後載入子類的成員變數,子類的程式碼塊,子類的建構函式。(成員變數和程式碼塊優先順序高於建構函式,成員變數和程式碼塊是按照順序載入)
4. 靜態的東西只會在類載入器載入類的時候初始化,當JVM的方法區已經載入該類的時候不會再次載入靜態資訊
5. 呼叫子類的構造方法會呼叫父類的構造方法。
總結:
1.先靜後動,先父後子
2.靜態程式碼塊和靜態成員變數>程式碼塊和成員變數>建構函式
3.靜態程式碼塊和靜態成員變數、程式碼塊和成員變數是按照程式碼中定義順序進行載入。
我們可以檢視上面程式碼編譯後的結果
A.class
import java.io.PrintStream; class A { public String s1; static { System.out.println("A的static程式碼塊..."); } public static String s2 = prtString("A的static變數..."); public A() { System.out.println("A的程式碼塊..."); this.s1 = prtString("A的成員變數..."); System.out.println("A的建構函式..."); } public static String prtString(String paramString) { System.out.println(paramString); return null; } }
B.class
import java.io.PrintStream; class B extends A { public String ss1 = prtString("B的成員變數..."); public static String ss2 = prtString("B的static變數..."); public B() { System.out.println("B的程式碼塊..."); System.out.println("B的建構函式..."); } private static A a = new A(); static { System.out.println("B的static程式碼塊..."); } }
補充:Java編譯器會在編譯的時候做一些優化,有時候我們可能考慮順序問題,比如:
public class TestClass { static{ s = "ssssssssssssssss"; } private static String s; public static void main(String[] args) { System.out.println(s); } }
編譯後程式碼:
import java.io.PrintStream; public class TestClass { private static String s = "ssssssssssssssss"; public static void main(String[] paramArrayOfString) { System.out.println(s); } }
補充:關於子類物件中呼叫父類構造方法的原因-------------這不是建立兩個物件,僅建立了一個子物件。父類的建構函式被呼叫是考慮到其可能有私有的屬性需要通過自身的建構函式初始化
在子類的建構函式(constructor)中super()
必須被首先呼叫,如果super()
沒有被呼叫,則編譯器將在建構函式(constructor)的第一行插入對super()
的呼叫。這就是為什麼當建立一個子類的物件時會呼叫父類的建構函式(constructor)的原因。
如果父類定義了自己的有參構造方法,而且沒有定義無參構造方法,則子類必須在構造方法第一行顯示呼叫父類的有參構造方法。否則編譯不通過。
如果父類既有無參構造也有有參構造,子類可以不顯示呼叫,編譯後會自動呼叫無參構造方法。
Java對上面的限制也滿足子類的物件是父類的物件。
簡單的說: 子類的建構函式必須引用父類的建構函式,由程式猿顯示呼叫或由編譯器隱式呼叫,對於這兩種方式,被引用的父類建構函式必須已被定義。
繼承的基本概念:
(1)Java不支援多繼承,也就是說子類至多隻能有一個父類。
(2)子類繼承了其父類中不是私有的成員變數和成員方法,作為自己的成員變數和方法。
(3)子類中定義的成員變數和父類中定義的成員變數相同時,則父類中的成員變數不能被繼承。
(4)子類中定義的成員方法,並且這個方法的名字返回型別,以及引數個數和型別與父類的某個成員方法完全相同,則父類的成員方法不能被繼承。
可以簡單的理解為建立子類的時候呼叫父類的構造方法是父類成員變數在子類空間中初始化。(我們可以稱此為父類子物件)
相同的方法會被重寫,變數沒有重寫之說,如果子類宣告瞭跟父類一樣的變數,那意味著子類將有兩個相同名稱的變數。一個存放在子類例項物件中,一個存放在父類子物件中。父類的private變數,也會被繼承並且初始化在子類父物件中,只不過對外不可見。
super關鍵字在java中的作用是使被遮蔽的成員變數或者成員方法變為可見,或者說用來引用被遮蔽的成員變數或成員方法,super只是記錄在物件內部的父類特徵(屬性和方法)的一個引用。啥叫被遮蔽的成員變數或成員方法?就是被子類重寫了的方法和定義了跟父類相同的成員變數,由於不能被繼承,所以就稱作被遮蔽。(所以super關鍵字只能在子類中使用)
關於繼承的記憶體分配可以參考:https://blog.csdn.net/xiaochuhe_lx/article/details/9126445