java中類的初始化探討

對於JAVA中類的初始化是一個很基礎的問題,其中的一些問題也是易被學習者所忽略。當在編寫代碼的時候碰到時,常被這些問題引發的錯誤,感覺莫名其妙。而且現在許多大公司的面試題,對於這方面的考查也是屢試不爽。不管基於什麼原因,我認爲,對於java類中的初始化問題,有必要深入的瞭解。Java類的初始化,其實就是它在JVM的初始化問題(類加載的問題),對於它在JVM中的初始化是一個相當複雜的問題,是給專家們來探討的,所以在這裏我只是對一些容易忽略的問題,發表一下個人觀點:

1,在一個類的內部(不考慮它是另一個類的派生類):很多人認爲,類的成員變量是在構造方法調用之後再初始化的,先不考慮這種觀點的正確性,先看一下下面的代碼:

class Test01...{

public Test01(int i)...{

System.out.println("Test01 of constractor : " + i);

}

}

public class Test02 ...{

private Test01 t1 = new Test01(1);

private int n = 10;

public Test02()...{

System.out.println("Test02 of constructor : " + n);

}

private Test01 t2 = new Test01(2);

public static void main(String[] args) ...{

Test02 test = new Test02();

}

}

輸出的結果爲:

Test01 of constractor : 1

Test01 of constractor : 2

Test02 of constructor : 10

通過輸出,可見當生成Test02的實例test時,它並不是首先調用其構造方法而是先是成員變量的初始化,而且成員的初始化的順序以成員變量的定義順序有關,先定義的先初始化,初始化後再調用構造方法。其實成員變量的初始化,在類的所有方法調用之前進行,包括構造方法

當類中有Static 修飾的成員呢?測試下面一段代碼:

public class Test03 ...{

private int i1 = printCommon();

private static int i2 = printStatic();

public Test03()...{

}

public static int printCommon()...{

System.out.println("i1 is init!");

return 1;

}

public static int printStatic()...{

System.out.println("i2 is init!");

return 2;

}

public static void main(String[] args) ...{

Test03 t = new Test03();

}

}

輸出結果爲:

i2 is init!

i1 is init!

可見static的成員比普通的成員變量先初始化。

我們都知道,如果一個類的成員變量沒有在定義時,系統會給予系統默認的值,有=號的就直接給予右值,系統在給予初值和=號給予值這2中方式,在執行時間上有先後嗎?爲了測試,我編寫了如下代碼:

public class Test04 ...{

private static Test04 t1 = new Test04();

private static int i1;

private static int i2 = 2;

public Test04()...{

i1++;

i2++;

}

public static void main(String[] args) ...{

Test04 t2 = new Test04();

System.out.println("t2.i1 = " + t2.i1);

System.out.println("t2.i2 = " + t2.i2);

}

}

我們先預計一下輸出,可能有幾種答案:2和3,3和3,2和2

執行代碼後:

t2.i1 = 2

t2.i2 = 3

爲什麼是2和3呢?其實代碼的執行順序是這樣的:首先執行給t1,i1,i2分別給予初始值null,0,0,再執行

Test04 t1 =new Test04(),這樣i1++,i2++被執行,i1,i2都變爲1,執行完畢後接着執行int i1; i1,i2的值仍然是1,1,當執行int i2 = 2時i2被賦予了值,即i1 = 1,i2=2;再執行Test04 t2 = new Test04(),i1,i2再執行++,此時i1 =2,i2 =3,輸出i1,i2,結果就是:t2.i1 = 2,t2.i2 = 3。 通過上面的代碼我們可以認爲系統默認值的給予比通過等號的賦予先執行。

2,一個類還有上層的類,即父類:

當生成一個子類時,大家到知道會調用父類的構造方法。如果子類和父類中都有Static的成員變量呢,其實我們在深入分析一個類的內部初始化後,對於存在父類的類的初始化其實原理都一樣,具體以下面的代碼爲例:

class SuperClass ...{

static...{

System.out.println("SuperClass of static block");

}

public SuperClass()...{

System.out.println("SuperClass of constracutor");

}

}

public class SubClass extends SuperClass...{

static...{

System.out.println("SubClass of static block");

}

public SubClass()...{

System.out.println("SubClass of constracutor");

}

public static void main(String[] args)...{

SuperClass t = new SubClass();

}

}

輸出結果:

SuperClass of static block

SubClass of static block

SuperClass of constracutor

SubClass of constracutor

可見當父類,和子類有Static時,先初始化Static,再初始化子類的Static,再初始化父類的其他成員變量->父類構造方法->子類其他成員變量->子類的構造方法。

父類上層還有父類時,總是先執行最頂層父類的Static-->派生類Static-->派生類Static-->.......-->子類Static-->頂層父類的其他成員變量-->父類構造方法--> 派生類的其他成員變量 --> 派生類構造方法--> ...............-->子類其他成員變量-->子類構造方法

討論到繼承,就不得提一下多態:

如果父類構造方法的代碼中有子類中被重寫得方法,當執行這樣的語句

SuperClass super = new SubClass();

初始化時調用父類的構造方法,是執行父類的原方法,還是執行子類中被重寫的方法呢?

class SuperClass...{

public SuperClass()...{

System.out.println("SuperClass of constructor");

m();

}

public void m()...{

System.out.println("SuperClass.m()");

}

}

public class SubClassTest extends SuperClass ...{

private int i = 10;

public SubClassTest()...{

System.out.println("SubClass of constructor");

super.m();

m();

}

public void m()...{

System.out.println("SubClass.m(): i = " + i);

}

public static void main(String[] args)...{

SuperClass t = new SubClassTest();

}

}

可能很多人會認爲輸出爲:

SuperClass of constructor

SubClass.m(): i = 10

SubClass of constructor

SuperClass.m()

SubClass.m(): i = 10

其實不然!

正確輸出爲:

SuperClass of constructor

SubClass.m(): i = 0

SubClass of constructor

SuperClass.m()

SubClass.m(): i = 10

在生成對象時,父類調用的M()方法,不是父類的 M()方法,而時子類中被重寫了的M()方法!!並且還出現一個怪異的現象,子類的privte int i 也被父類訪問到,這不是和我們說private的成員只能在本類使用的原則相違背了嗎?其實我們說的這條原則是編譯期間所遵守的,在JAVA程序的編譯期間,它只檢查語法的合法性,在JAVA的JVM中,即運行期間,不管你聲明的什麼,對於JVM來說都是透明的,而多態是在運行期間執行的,所以能拿到SubClass的private成員,一點都不奇怪,只是此時還沒執行 i = 10,所以在父類的構造方法中調用m()時,系統只能將i賦予系統初值0。

下面是我設計的一道完整的初始化例子,可測試你對類的初始化問題是否完整掌握:

寫出程序運行的結果:

class A...{

private int i = 9;

protected static int j;

static...{

System.out.println("-- Load First SuperClass of static block start!-- ");

System.out.println("j = " + j);

System.out.println("-- Load First SuperClass of static block End -- ");

}

public A()...{

System.out.println("------- Load SuperClass of structor start --------");

System.out.println("Frist print j = " + j);

j = 10;

m();

System.out.println("k = " + k);

System.out.println("Second print j = " + j);

System.out.println("----------- Load SuperClass End ----------- ");

}

private static int k = getInt();

public static int getInt()...{

System.out.println("Load SuperClass.getInt() ");

return 11;

}

static...{

System.out.println("--- Load Second SuperClass of static block!-------");

System.out.println("j = " + j);

System.out.println("k = " + k);

System.out.println("-- Load Second SuperClass of static block End -- ");

}

public void m()...{

System.out.println("SuperClass.m() , " + "j = " +j);

}

}

class B extends A ...{

private int a = 10;

static...{

System.out.println("---- Load SubClass of static block!------");

System.out.println("-- Load SubClass of static block End -- ");

}

public B()...{

System.out.println("Load SubClass of structor");

m();

System.out.println("--- Load SubClass End ---- ");

}

public void m()...{

System.out.println("SubClass.m() ," + "a = " + a );

}

}

public class Test1...{

public static void main(String[] args)...{

A a = new B();

}

}

正確的答案爲:

-- Load First SuperClass of static block start!--

j = 0

-- Load First SuperClass of static block End --

Load SuperClass.getInt()

--- Load Second SuperClass of static block!-------

j = 0

k = 11

-- Load Second SuperClass of static block End --

---- Load SubClass of static block!------

-- Load SubClass of static block End --

------- Load SuperClass of structor start --------

Frist print j = 0

SubClass.m() ,a = 0

k = 11

Second print j = 10

----------- Load SuperClass End -----------

Load SubClass of structor

SubClass.m() ,a = 10

--- Load SubClass End ----

以上是對一些常見的初始化問題進行了討論,如果你想更深入的瞭解,需要藉助一些專門書物。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章