溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

java繼承關(guān)系的類初始化和實例化順序是怎樣的

發(fā)布時間:2021-09-18 17:35:13 來源:億速云 閱讀:122 作者:柒染 欄目:編程語言

這期內(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è)資訊頻道。

向AI問一下細節(jié)

免責(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)容。

AI