一個實例對象的創建包括:類初始化和實例初始化
1. 一個類要創建實例需要先加載並初始化該類,main方法所在的類需要先加載和初始化
2. 一個子類要初始化需要先初始化父類
3. 一個類初始化就是執行<clinit>()方法
<clinit>方法由靜態變量顯示賦值代碼和靜態代碼塊組成
類靜態變量顯示賦值代碼和靜態代碼塊代碼從上到下順序執行
<clinit>方法只執行一次
4.實例初始化就是執行<init>方法
<init>方法由非靜態實例變量顯示賦值代碼和非靜態代碼塊,對應構造器代碼組成。
非靜態實例變量顯示賦值代碼和非靜態代碼塊從上到下順序執行,而對應構造器代碼最后執行
每次創建實例對象,調用對應的構造器,執行的就是對應的<init>方法
<init>方法的首行是super()或super(實參列表),即對應父類的<init>方法
類加載過程(類初始化):
1、類加載就是執行Java程序編譯之后在字節碼文件中生成的clinit()方法(稱之為類構造器),clinit()方法由靜態變量和靜態代碼塊組成。
2、子類的加載首先需要先加載父類,如果父類為接口。則不會調用父類的clinit方法。一個類中可以沒有clinit方法。
3、clinit方法中的執行順序為:父類靜態變量初始化,父類靜態代碼塊,子類靜態變量初始化,子類靜態代碼塊。
4、clinit()方法只執行一次。
對象實例化過程(實例初始化):
1、對象實例化過程就是執行Java程序編譯之后在字節碼文件中生成的init()方法(稱之為實例構造器),init()方法由非靜態變量、非靜態代碼塊以及對應的構造器組成。
2、init()方法可以重載多個,有幾個構造器就有幾個init()方法,每次創建實例,調用哪一個構造器,就會調用相應的init()方法。
3、init()方法中的執行順序為:父類變量初始化,父類代碼塊,父類構造器,子類變量初始化,子類代碼塊,子類構造器。
clinit()方法優先於init()方法執行,所以整個順序就是:
父類靜態變量初始化,父類靜態代碼塊,子類靜態變量初始化,子類靜態代碼塊,父類非靜態變量初始化,父類非靜態代碼塊,父類構造器,子類非靜態變量初始化,子類非靜態代碼塊,子類構造器。
類加載時只會加載靜態代碼塊和靜態變量。只有創建對象的實例時: 變量和初始化代碼塊和構造函數才會執行(創建對象實例會先觸發類加載)
類加載:執行<clinit>
創建對象實例:執行<<init>,但是創建類實例之前會先類加載執行<clinit>。
類初始化:執行<clinit>方法,<clinit>方法由靜態變量顯示賦值代碼和靜態代碼塊組成,<clinit>方法只執行一次
實例初始化:執行<init>方法,<init>方法由非靜態實例變量顯示賦值代碼和非靜態代碼塊,對應構造器代碼組成。
觸發類加載:
1. 為一個類型創建一個新的對象實例時(比如new、反射、序列化)
只有這個會觸發 變量和初始化代碼塊和構造函數執行,這個是實例初始化,其他6個都是類初始化
2. 調用一個類型的靜態方法時(即在字節碼中執行invokestatic指令)
3. 調用一個類型或接口的靜態字段,或者對這些靜態字段執行賦值操作時(即在字節碼中,執行getstatic或者putstatic指令),
不過用final修飾的靜態字段已經賦值了(String和基本類型,不包含包裝類型)的除外,它被初始化為一個編譯時常量表達式
4. 調用JavaAPI中的反射方法時(比如調用java.lang.Class中的方法(Class.forName),或者java.lang.reflect包中其他類的方法)
5. 初始化一個類的派生類時(Java虛擬機規范明確要求初始化一個類時,它的超類必須提前完成初始化操作,接口例外)
6. JVM啟動包含main方法的啟動類時。
7. 對於靜態字段,只有直接定義這個字段的類才會被初始化,因此通過其子類來引用父類中定義的靜態字段,只會觸發父類的初始化而不會觸發子類的初始化。
加載順序: 靜態變量和靜態初始化塊是依照他們在類中的定義順序進行初始化的。同樣,變量和初始化塊也遵循這個規律。
1. 父類--靜態變量
2. 父類--靜態初始化塊
3. 子類--靜態變量
4. 子類--靜態初始化塊
5. 父類--變量
6. 父類--初始化塊
7. 父類--構造器
8. 子類--變量
9. 子類--初始化塊
10. 子類--構造器
多態
方法重寫@Override
哪些方法不可以被重寫
1、final方法
2、靜態方法
3、private等子類中不可見的方法
對象的多態性
1、子類如果重寫了父類的方法,通過子類對象調用的一定是子類重寫過的代碼
2、非靜態方法默認的調用對象是this
3、this對象在構造器或者說<init>方法中就是正在創建的對象,所以通過子類實例化時觸發的父類加載調用的非靜態方法,
調用對象this是子類對象,調用的方法是子類重寫的方法。
子類實例化時如果有父類,會觸發父類加載 在初始化過程中如果父類方法被子類重寫了,會調用子類的方法。
通過父類的引用類型變量指向子類類型對象,訪問成員變量時是訪問的父類的成員變量。
父類代碼:
/** * 父類的初始化<clinit> * 1、j=method() * 2、父類的靜態代碼塊 * * 父類的實例方法: * 1、super()最前 * 2、i = test(); * 3、父類的非靜態代碼塊 * 4、父類的無參構造(最后) * * * 非靜態方法前面其實有一個默認的對象this * this在構造器或者<init>,它表示的是正在創建的對象,因為這里是在創建Son對象,所以 * test()執行的是子類重寫的代碼(面向對象多態) * 這里 i=test()執行的是子類重寫的test()方法 * @author yuan * @date 2019/11/23 */ public class Father { /** * i=test()執行的是子類重寫的test,因為是子類實例化觸發的父類加載 */ private int i = test(); private static int j = method(); static { System.out.print("(1)"); } public Father() { System.out.print("(2)"); } { System.out.print("(3)"); } public int test() { System.out.print("(4)"); return 1; } public static int method(){ System.out.print("(5)"); return 1; } public static void main(String[] args) throws Exception { new Father().test(); } }
子類代碼:
/** * 子類的初始化<clinit> * 1、j=method() * 2、父類的靜態代碼塊 * * 先初始化父類:(5),(1) * 初始化子類:(10),(6) * * 子類的實例方法<init>: * 1、super()最前,super代表父類初始化,不是創建父類實例,是加載父類 * 2、i = test(); * 3、子類的非靜態代碼塊 * 4、子類的無參構造(最后) * * 因為創建了兩個Son對象,因此實例方法<init>執行了兩次,但是<clinit>方法只會執行一次 * * @author yuan * @date 2019/11/23 */ public class Son extends Father { private int i = test(); private static int j = method(); static { System.out.print("(6)"); } public Son() { //super(); 寫或不寫都存在,在子類構造器中一定會調用父類的構造器 System.out.print("(7)"); } { System.out.print("(8)"); } @Override public int test() { System.out.print("(9)"); return 1; } public static int method() { System.out.print("(10)"); return 1; } public static void main(String[] args) throws Exception { Son s = new Son(); System.out.println(); Son son = new Son(); } }
輸出:
(5)(1)(10)(6)(9)(3)(2)(9)(8)(7)
(9)(3)(2)(9)(8)(7)