由於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