您好,登錄后才能下訂單哦!
這期內(nèi)容當(dāng)中小編將會給大家?guī)碛嘘P(guān)java繼承關(guān)系的類初始化和實例化順序是怎樣的,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
就像之前的一個評論.我們學(xué)習(xí)的是思路. 很多人都知道繼承關(guān)系的類的初始化和實例化的順序,但如果忘記了怎么辦? 如何找到自己的答案? 又如果遇到的問題是關(guān)于泛型的擦除問題,又該如何去分析?
思路,重點是思路.泛型擦除先不談.看繼承. 首先給出一個例子,看看它的輸出是什么.
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只是負責(zé)解析字節(jié)碼.字節(jié)碼雖然不是最原始的原子匯編碼,但字節(jié)碼已經(jīng)可以完全解釋JVM的指令執(zhí)行過程了.一般來說,字節(jié)碼和java源碼相差比較大,javac會做前期優(yōu)化,修改增加刪除源碼產(chǎn)生jvm解釋器可以理解的字節(jié)碼. java語法帶來的安全,易用,易讀等功能讓我們忽略了字節(jié)碼會和java源碼有出路.
當(dāng)遇到new的時候,比如new B(),將會嘗試去初始化B類.如果B已經(jīng)初始化,則開始實例化B類.如果B類沒有初始化,則初始化B類,但B類繼承A,所以在初始化B類之前需要先初始化A類.所以類的初始化過程是:A->B. 類在初始化的時候會執(zhí)行static域和塊. 類的實例化在類初始化之后,實例化的時候必須先實例化父類.實例化會先執(zhí)行域和塊,然后再執(zhí)行構(gòu)造函數(shù).
上面的理論如果靠這種死記硬背,總會忘記.哦,還有父類的構(gòu)造函數(shù)必須放在子類構(gòu)造函數(shù)的***行.為什么?
遇到這種語法問題的時候,看教科書不如自己找出答案.工具就在JDK中,一個名叫javap的命令. javap會打出一個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ū)中, 而類的初始化則會在線程棧中執(zhí)行static{}塊的code. 在之前,這個塊有另一個名字<cinit>即類初始化方法.現(xiàn)在改名為static{}了. 類的初始化只進行一次. 但是,每當(dāng)一個類在裝載和鏈接完畢以后,通過字節(jié)碼的分析,JVM解析器已經(jīng)知道B是繼承A的,于是在初始化B類前,A類會先初始化.這是一個遞歸過程. 所以,B類的初始化會導(dǎo)致A類static{}執(zhí)行,然后是B的static{}執(zhí)行.讓我們看看B的static{}塊中執(zhí)行了什么.
static {}; Code: Stack=2, Locals=0, Args_size=0 棧深為2,本地變量0個,參數(shù)傳遞0個. 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)我們進行類的實例化的時候,會調(diào)用類的構(gòu)造函數(shù).我們看看類B的構(gòu)造函數(shù)做了什么.
public B(); Code: Stack=2, Locals=1, Args_size=1 棧深為2,本地變量1個(其實就是this),參數(shù)為1個(就是this). 0: aload_0 將***個參數(shù)壓棧.也就是this壓棧. 1: invokespecial #32; //Method A."<init>":()V 在this上調(diào)用父類的構(gòu)造函數(shù).在B的構(gòu)造函數(shù)中并沒有聲明super(),但是java編譯器會自動生成此字節(jié)碼來調(diào)用父類的無參構(gòu)造函數(shù).如果在B類中聲明了super(int),編譯器會使用對應(yīng)的A類構(gòu)造函數(shù)來代替.JVM只是執(zhí)行字節(jié)碼而已,它并不對super進行約束,約束它們的是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"出棧.???nbsp; 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é)碼的時候,將父類的構(gòu)造函數(shù),域的初始化,代碼塊的執(zhí)行和B的真正的構(gòu)造函數(shù)按照順序組合在了一起,形成了新的構(gòu)造函數(shù). 一個類的編譯后的構(gòu)造函數(shù)字節(jié)碼一定會遵循這樣的順序包含以下內(nèi)容:
父類的構(gòu)造函數(shù)->
當(dāng)前類的域初始化->(按照書寫順序)
代碼塊->(按照書寫順序)
當(dāng)前類的構(gòu)造函數(shù).
到這里,應(yīng)該徹底明白繼承類的初始化和實例化順序了.
上述就是小編為大家分享的java繼承關(guān)系的類初始化和實例化順序是怎樣的了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關(guān)知識,歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。