天天看點

關于java中建立對象時屬性的初始化過程

java是一種面向對象的程式設計語言,那麼了解建立對象時程式會怎麼執行就變得尤為重要,下面我們就一起看看在我們使用new關鍵字建立對象時是怎麼對屬性初始化的:

下面是一個Person類,其中有成員變量age和靜态變量name

public class Person {

    private int age;            //年齡

    public static String name = "abc";           //姓名

    public Person(int age) {
        this.age = age;
    }
}
           

編譯後接着用javap指令對其反編譯:

{
  public static java.lang.String name;
    descriptor: Ljava/lang/String;
    flags: ACC_PUBLIC, ACC_STATIC

  public Person(int);
    descriptor: (I)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iload_1							  //将int類型的變量(構造方法中傳的age)推至棧頂
         6: putfield      #2                  //對age屬性進行指派
         9: return
      LineNumberTable:
        line 9: 0
        line 10: 4
        line 11: 9

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: ldc           #3                  // 将"abc"從常量池推至棧頂
         2: putstatic     #4                  // 對name屬性指派
         5: return
      LineNumberTable:
        line 7: 0
}
           

在這裡我們可以發現,對于成員屬性來說,是在構造方法中初始化的,對于static屬性編譯器會在static代碼塊中對其初始化(按static語句的聲明順序)。那麼如果我們直接在成員屬性上顯示指派而不在構造方法中初始化呢:

為了友善觀察,我在構造方法裡加入了一個輸出語句。

public class Person {

    private int age = 13;            //年齡

    public static String name = "abc";           //姓名

    public Person() {
        System.out.println("構造方法");
    }
}
           

javap反編譯後:

public Person();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: bipush        13				  //将單位元組的常量值(-128~127)推送至棧頂
         7: putfield      #2                  // Field age:I(對屬性age指派)
		------------------ 下面是關于輸出語句的代碼 ------------------
        10: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        13: ldc           #4                  // String 構造方法
        15: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        18: return
      LineNumberTable:
        line 9: 0
        line 5: 4
        line 10: 10
        line 11: 18
           

可以看到,對于直接對成員屬性顯示指派的情況,編譯器會将它放到構造方法中代碼的前面執行,比如上面的代碼就把對age屬性的指派放在了輸出語句的前面。

為了更直覺的觀察建立對象時,jvm對屬性的指派過程,我們在idea裡debug一下:

關于java中建立對象時屬性的初始化過程
關于java中建立對象時屬性的初始化過程
關于java中建立對象時屬性的初始化過程

到這裡,整個對象的初始化就完成了。可以發現,當位元組碼檔案被加載到jvm中後,類中static屬性其實并沒有馬上被指派,而是先有了其類型對應的預設值,成員屬性在new關鍵字開辟記憶體空間後,也是先有了預設值,直到構造方法對其指派。

那麼對于static final修飾的變量也是如此嗎?

關于java中建立對象時屬性的初始化過程

總結:

在我們使用new關鍵字建立一個對象的時候。

1.jvm會先去加載這個對象的類的位元組碼檔案,并把其中static的屬性放到方法區儲存,此時這些屬性還是其類型對應的預設值,(static final的變量除外,jvm會直接對其指派)。

2.緊接着jvm會對這些方法區中的static變量進行指派。

3.在方法區中static屬性指派完成後,new關鍵字就會在堆中去申請一塊空間用來存儲對象,此時對象中的成員屬性就正式在記憶體中存在了,但會被賦予其類型的預設值。

4.在new關鍵字在堆中開辟記憶體空間後,jvm就會去調用對象的構造方法,在構造方法中對成員屬性開始指派,當構造方法執行完畢,成員屬性也随之完成了指派。到這裡,整個對象的建立過程就算結束了。