天天看點

類和對象的初始化

轉載請注明原創出處,謝謝!

HappyFeet的部落格

本文主要通過例子來了解 Java 中類和對象的初始化。

1、首先來看一個最簡單的例子(無父類且無靜态成員變量)
public class OrderOfInitialization1 {

    public static void main(String[] args) {
        House house = new House();
        house.f();
    }

}

class Window {
    Window(int market) {
        System.out.println("Window(" + market + ")");
    }
}

class House {
    Window w1 = new Window(1);// before constructor

    House() {
        // show we're in constructor
        System.out.println("House()");
        w3 = new Window(33);//reinitialize w3
    }

    Window w2 = new Window(2);// after constructor

    void f() {
        System.out.println("f()");
    }

    Window w3 = new Window(3);//at end
}
           
output:
    Window(1)
    Window(2)
    Window(3)
    House()
    Window(33)
    f()
           

從輸出結果分析,House 執行個體變量的初始化順序是:

w1

w2

w3

,然後才是構造函數。

即:執行個體變量按照其在代碼中出現的順序執行初始化,然後執行構造函數裡面的初始化。

2、接下來看一個稍微複雜一些的例子(有父類但無靜态成員變量)
public class OrderOfInitialization2 {

    public static void main(String[] args) {
        SubClass subClass = new SubClass();
    }

}

class Print {
    Print(int i) {
        System.out.println("new Print(" + i + ")");
    }
}

class SuperClass {
    Print print1 = new Print(1);

    public SuperClass() {
        System.out.println("new SuperClass()");
    }

    Print print2 = new Print(2);
}

class SubClass extends SuperClass {
    Print print3 = new Print(3);

    public SubClass() {
        //這個地方其實是調用了父類的預設的無參構造函數,super();
        //如果父類沒有無參構造函數,則這個地方必須顯式的調用父類的構造函數,否則編譯不通過
        System.out.println("new SubClass()");
    }

    Print print4 = new Print(4);
}
           
output:
    new Print(1)
    new Print(2)
    new SuperClass()
    new Print(3)
    new Print(4)
    new SubClass()
           

從輸出結果分析:這個地方是先調用了父類 SuperClass 的構造函數,然後調用子類 SubClass 的構造函數。

即:如果一個類有父類,在執行個體化子類的時候,會先執行父類的構造函數,然後執行子類的構造函數。

3、繼續看一個更複雜一些的例子(有父類且有靜态成員變量)
public class OrderOfInitialization3 {

    public static void main(String[] args) {
        Man man = new Man();
        Man man1 = new Man();
    }

    static Print1 print0 = new Print1(0);
}

class Print1 {
    Print1(int i) {
        System.out.println("new Print1(" + i + ")");
    }
}

class People {
    Print1 print1 = new Print1(1);

    public People() {
        System.out.println("new People()");
    }

    Print1 print2 = new Print1(2);

    static Print1 print5 = new Print1(5);
}

class Man extends People {

    Print1 print3 = new Print1(3);

    public Man() {
        System.out.println("new Man()");
    }

    Print1 print4 = new Print1(4);

    static Print1 print6 = new Print1(6);
}
           
output:
       new Print(0)
       new Print(5)
       new Print(6)
       new Print(1)
       new Print(2)
       new People()
       new Print(3)
       new Print(4)
       new Man()
       new Print(1)
       new Print(2)
       new People()
       new Print(3)
       new Print(4)
       new Man()
           

從輸出結果分析:這裡首先執行了 OrderOfInitialization3 類的靜态變量 print0 的初始化(輸出 new Print(0)),然後執行靜态方法 main;緊接着是執行 People 類的靜态成員變量 print5 的初始化(輸出 new Print(5)),再接着是 Man 類的靜态成員變量 print6 的初始化(輸出 new Print(6));之後是 People 的執行個體變量(輸出new Print(1)、new Print(2))、構造函數(輸出 new People())初始化,最後才是 Man 執行個體變量(輸出 new Print(3)、new Print(4))、構造函數(輸出 new Man())的初始化。在第二次執行個體化一個 Man 的時候,所有的靜态成員變量都沒有相應的輸出,即靜态成員變量隻初始化了一次。

是以這個地方執行的順序是:首先執行 main 所在類的靜态成員變量的初始化,然後是 Man 的父類的靜态成員變量的初始化,然後是子類的靜态成員的初始化;接着是父類的構造函數,最後才是子類的構造函數。

這個地方 Man 執行個體化了兩次,但是其父類和本身的靜态成員變量隻初始化了一次。

為什麼靜态成員變量隻會初始化一次呢?

  • 實際上,靜态成員變量初始化的過程本質上就是一個類的加載和初始化的過程,虛拟機保證了在同一個類加載器下,一個類型隻會初始化一次。
4、總結一下

這個地方把類和對象分開會更好了解一點。

(1)類的初始化

  • 靜态成員變量初始化發生在靜态方法之前
  • 父類的初始化必須在子類初始化之前
  • 靜态成員變量的初始化順序為其在代碼中出現的順序

(2)執行個體化對象

  • 如果有父類,先執行父類的執行個體化
  • 成員變量初始化發生在構造函數之前
  • 成員變量的初始化順序為其在代碼中出現的順序

(3)執行個體化對象之前如果該類沒有初始化,必須先執行該類的初始化。

5、最後看一個比較特殊的例子
public class NestedInitialization {

    public static void main(String[] args) {
        staticFunction();
    }

    static NestedInitialization st = new NestedInitialization();

    static {
        System.out.println("1");
    }

    {
        System.out.println("2");
    }

    NestedInitialization() {
        System.out.println("3");
        System.out.println("a=" + a + ",b=" + b);
    }

    public static void staticFunction() {
        System.out.println("4");
    }

    int a = 110;
    static int b = 112;

}
           
output:
	2
	3
	a=110,b=0
	1
	4
           

這個例子的特殊性在于,**該類還沒有完成初始化,就去執行個體化一個該類的對象。**我們從類的生命周期來分析,一個類會經曆加載、驗證、準備、解析、初始化、使用和解除安裝七個階段,在執行到

static NestedInitialization st = new NestedInitialization();

這一步時,已經是類的初始化階段了,此時,NestedInitialization 類裡面的靜态成員的值都還是準備階段設定的初始零值,即

static NestedInitialization st = null

,

static int b = 0;

,然後這個地方需要執行個體化 NestedInitialization,是以是以此時的狀态去執行個體化 NestedInitialization 類的,執行類的執行個體化,然後在繼續類的初始化。是以才會出現上面的輸出結果。

參考資料:

(1)《深入了解 Java 虛拟機》周志明 著.

(2)《Thinking in Java(4th Edition)》