繼承關(guān)系的類初始化和實(shí)例化的順序
就像之前的一個(gè)評(píng)論.我們學(xué)習(xí)的是思路. 很多人都知道繼承關(guān)系的類的初始化和實(shí)例化的順序,但如果忘記了怎么辦? 如何找到自己的答案? 又如果遇到的問題是關(guān)于泛型的擦除問題,又該如何去分析?
思路,重點(diǎn)是思路.泛型擦除先不談.看繼承. 首先給出一個(gè)例子,看看它的輸出是什么.
- public class A {
- private static String a = "NA";
- private String i="NA";
- {
- i = "A";
- System.out.println(i);
- }
- static {
- a = "Static A";
- System.out.println(a);
- }
- public A() {
- System.out.println("Construct A");
- }
- }
- public class B extends A {
- private static String b = "NB";
- private String j="NB";
- {
- j = "B";
- System.out.println(j);
- }
- static {
- b = "Static B";
- System.out.println(b);
- }
- public B() {
- System.out.println("Construct B");
- }
- }
- public class C {
- public static void main(String[] args) {
- new B();
- }
- }
以上輸出是:
Static A Static B A Construct A B Construct B |
一切都是java編譯器搞得鬼. JVM只是負(fù)責(zé)解析字節(jié)碼.字節(jié)碼雖然不是最原始的原子匯編碼,但字節(jié)碼已經(jīng)可以完全解釋JVM的指令執(zhí)行過程了.一般來說,字節(jié)碼和java源碼相差比較大,javac會(huì)做前期優(yōu)化,修改增加刪除源碼產(chǎn)生jvm解釋器可以理解的字節(jié)碼. java語法帶來的安全,易用,易讀等功能讓我們忽略了字節(jié)碼會(huì)和java源碼有出路.
當(dāng)遇到new的時(shí)候,比如new B(),將會(huì)嘗試去初始化B類.如果B已經(jīng)初始化,則開始實(shí)例化B類.如果B類沒有初始化,則初始化B類,但B類繼承A,所以在初始化B類之前需要先初始化A類.所以類的初始化過程是:A->B. 類在初始化的時(shí)候會(huì)執(zhí)行static域和塊. 類的實(shí)例化在類初始化之后,實(shí)例化的時(shí)候必須先實(shí)例化父類.實(shí)例化會(huì)先執(zhí)行域和塊,然后再執(zhí)行構(gòu)造函數(shù).
上面的理論如果靠這種死記硬背,總會(huì)忘記.哦,還有父類的構(gòu)造函數(shù)必須放在子類構(gòu)造函數(shù)的***行.為什么?
遇到這種語法問題的時(shí)候,看教科書不如自己找出答案.工具就在JDK中,一個(gè)名叫javap的命令. javap會(huì)打出一個(gè)class的字節(jié)碼偽碼. 我們只需要分析B的字節(jié)碼,就可以找到答案.
- joeytekiMacBook-Air:bin joey$ javap -verbose B
- Compiled from "B.java"
- public class B extends A
- SourceFile: "B.java"
- minor version: 0
- major version: 50
- Constant pool:
- const #1 = class #2; // B
- const #2 = Asciz B;
- const #3 = class #4; // A
- const #4 = Asciz A;
- const #5 = Asciz b;
- const #6 = Asciz Ljava/lang/String;;
- const #7 = Asciz j;
- const #8 = Asciz <clinit>;
- const #9 = Asciz ()V;
- const #10 = Asciz Code;
- const #11 = String #12; // NB
- const #12 = Asciz NB;
- const #13 = Field #1.#14; // B.b:Ljava/lang/String;
- const #14 = NameAndType #5:#6;// b:Ljava/lang/String;
- const #15 = String #16; // Static B
- const #16 = Asciz Static B;
- const #17 = Field #18.#20; // java/lang/System.out:Ljava/io/PrintStream;
- const #18 = class #19; // java/lang/System
- const #19 = Asciz java/lang/System;
- const #20 = NameAndType #21:#22;// out:Ljava/io/PrintStream;
- const #21 = Asciz out;
- const #22 = Asciz Ljava/io/PrintStream;;
- const #23 = Method #24.#26; // java/io/PrintStream.println:(Ljava/lang/String;)V
- const #24 = class #25; // java/io/PrintStream
- const #25 = Asciz java/io/PrintStream;
- const #26 = NameAndType #27:#28;// println:(Ljava/lang/String;)V
- const #27 = Asciz println;
- const #28 = Asciz (Ljava/lang/String;)V;
- const #29 = Asciz LineNumberTable;
- const #30 = Asciz LocalVariableTable;
- const #31 = Asciz <init>;
- const #32 = Method #3.#33; // A."<init>":()V
- const #33 = NameAndType #31:#9;// "<init>":()V
- const #34 = Field #1.#35; // B.j:Ljava/lang/String;
- const #35 = NameAndType #7:#6;// j:Ljava/lang/String;
- const #36 = String #2; // B
- const #37 = String #38; // Construct B
- const #38 = Asciz Construct B;
- const #39 = Asciz this;
- const #40 = Asciz LB;;
- const #41 = Asciz SourceFile;
- const #42 = Asciz B.java;
- {
- static {};
- Code:
- Stack=2, Locals=0, Args_size=0
- 0: ldc #11; //String NB
- 2: putstatic #13; //Field b:Ljava/lang/String;
- 5: ldc #15; //String Static B
- 7: putstatic #13; //Field b:Ljava/lang/String;
- 10: getstatic #17; //Field java/lang/System.out:Ljava/io/PrintStream;
- 13: getstatic #13; //Field b:Ljava/lang/String;
- 16: invokevirtual #23; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
- 19: return
- LineNumberTable:
- line 3: 0
- line 11: 5
- line 12: 10
- line 13: 19
- public B();
- Code:
- Stack=2, Locals=1, Args_size=1
- 0: aload_0
- 1: invokespecial #32; //Method A."<init>":()V
- 4: aload_0
- 5: ldc #11; //String NB
- 7: putfield #34; //Field j:Ljava/lang/String;
- 10: aload_0
- 11: ldc #36; //String B
- 13: putfield #34; //Field j:Ljava/lang/String;
- 16: getstatic #17; //Field java/lang/System.out:Ljava/io/PrintStream;
- 19: aload_0
- 20: getfield #34; //Field j:Ljava/lang/String;
- 23: invokevirtual #23; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
- 26: getstatic #17; //Field java/lang/System.out:Ljava/io/PrintStream;
- 29: ldc #37; //String Construct B
- 31: invokevirtual #23; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
- 34: return
- LineNumberTable:
- line 15: 0
- line 4: 4
- line 6: 10
- line 7: 16
- line 16: 26
- line 17: 34
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 35 0 this LB;
- }
類的生命周期,將經(jīng)歷類的裝載,鏈接,初始化,使用,卸載. 裝載是將字節(jié)碼讀入到內(nèi)存的方法區(qū)中, 而類的初始化則會(huì)在線程棧中執(zhí)行static{}塊的code. 在之前,這個(gè)塊有另一個(gè)名字<cinit>即類初始化方法.現(xiàn)在改名為static{}了. 類的初始化只進(jìn)行一次. 但是,每當(dāng)一個(gè)類在裝載和鏈接完畢以后,通過字節(jié)碼的分析,JVM解析器已經(jīng)知道B是繼承A的,于是在初始化B類前,A類會(huì)先初始化.這是一個(gè)遞歸過程. 所以,B類的初始化會(huì)導(dǎo)致A類static{}執(zhí)行,然后是B的static{}執(zhí)行.讓我們看看B的static{}塊中執(zhí)行了什么.
- static {};
- Code:
- Stack=2, Locals=0, Args_size=0
- 棧深為2,本地變量0個(gè),參數(shù)傳遞0個(gè).
- 0: ldc #11; //String NB
- 將常量池中#11放到棧頂.#11="NB".
- 2: putstatic #13; //Field b:Ljava/lang/String;
- 將棧頂?shù)闹?nbsp;"NB" 賦予常量池中的#13,也就是 static b="NB".
- 5: ldc #15; //String Static B
- 將#15放入棧頂. #15="static B".
- 7: putstatic #13; //Field b:Ljava/lang/String;
- 賦值static b = "static B".
- 10: getstatic #17; //Field java/lang/System.out:Ljava/io/PrintStream;
- 將PrintStream引用壓棧.
- 13: getstatic #13; //Field b:Ljava/lang/String;
- 將static b的值壓棧.
- 16: invokevirtual #23; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
- 調(diào)用虛函數(shù)PrintStream.println("static B")
- 19: return
- 退出函數(shù),銷毀函數(shù)棧幀.
通過注釋,我們看到類B中的static域賦值和static塊均被放到了類的初始化函數(shù)中.
當(dāng)我們進(jìn)行類的實(shí)例化的時(shí)候,會(huì)調(diào)用類的構(gòu)造函數(shù).我們看看類B的構(gòu)造函數(shù)做了什么.
- public B();
- Code:
- Stack=2, Locals=1, Args_size=1
- 棧深為2,本地變量1個(gè)(其實(shí)就是this),參數(shù)為1個(gè)(就是this).
- 0: aload_0
- 將***個(gè)參數(shù)壓棧.也就是this壓棧.
- 1: invokespecial #32; //Method A."<init>":()V
- 在this上調(diào)用父類的構(gòu)造函數(shù).在B的構(gòu)造函數(shù)中并沒有聲明super(),但是java編譯器會(huì)自動(dòng)生成此字節(jié)碼來調(diào)用父類的無參構(gòu)造函數(shù).如果在B類中聲明了super(int),編譯器會(huì)使用對(duì)應(yīng)的A類構(gòu)造函數(shù)來代替.JVM只是執(zhí)行字節(jié)碼而已,它并不對(duì)super進(jìn)行約束,約束它們的是java的編譯器.this出棧.
- 4: aload_0
- 將this壓棧.
- 5: ldc #11; //String NB
- 將"NB"壓棧.
- 7: putfield #34; //Field j:Ljava/lang/String;
- 給j賦值this.j="NB". this和"NB"出棧.
- 10: aload_0
- 將this壓棧.
- 11: ldc #36; //String B
- 把"B"壓棧
- 13: putfield #34; //Field j:Ljava/lang/String;
- 給j賦值this.j="B". this和"B"出棧.???
- 16: getstatic #17; //Field java/lang/System.out:Ljava/io/PrintStream;
- 壓棧PrintStream
- 19: aload_0
- 壓棧this
- 20: getfield #34; //Field j:Ljava/lang/String;
- this出棧,調(diào)用this.j,壓棧this.j.
- 23: invokevirtual #23; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
- 調(diào)用PrintStream.println(this.j).???
- 26: getstatic #17; //Field java/lang/System.out:Ljava/io/PrintStream;
- 壓棧PrintStream
- 29: ldc #37; //String Construct B
- 壓棧"Construct B"
- 31: invokevirtual #23; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
- 調(diào)用PrintStream.println("Construct B")
- 34: return
從上面的字節(jié)碼可以看出,java編譯器在編譯產(chǎn)生字節(jié)碼的時(shí)候,將父類的構(gòu)造函數(shù),域的初始化,代碼塊的執(zhí)行和B的真正的構(gòu)造函數(shù)按照順序組合在了一起,形成了新的構(gòu)造函數(shù). 一個(gè)類的編譯后的構(gòu)造函數(shù)字節(jié)碼一定會(huì)遵循這樣的順序包含以下內(nèi)容:
父類的構(gòu)造函數(shù)->
當(dāng)前類的域初始化->(按照書寫順序)
代碼塊->(按照書寫順序)
當(dāng)前類的構(gòu)造函數(shù).
到這里,應(yīng)該徹底明白繼承類的初始化和實(shí)例化順序了.