對於JAVA中類的初始化是一個很基礎的問題,其中的一些問題也是易被學習者所忽略。當在編寫代碼的時候碰到時,常被這些問題引起的錯誤,感受莫名其妙。並且如今許多大公司的面試題,對於這方面的考查也是屢試不爽。無論基於什麼緣由,我認爲,對於java類中的初始化問題,有必要深刻的瞭解。Java類的初始化,其實就是它在JVM的初始化問題(類加載的問題),對於它在JVM中的初始化是一個至關複雜的問題,是給專家們來探討的,因此在這裏我只是對一些容易忽略的問題,發表一下我的觀點:
1,在一個類的內部(不考慮它是另外一個類的派生類):不少人認爲,類的成員變量是在構造方法調用以後再初始化的,先不考慮這種觀點的正確性,先看一下下面的代碼:java
[java] view plaincopy面試
class Test01...{ 學習
public Test01(int i)...{ 測試
System.out.println("Test01 of constractor : " + i); spa
} .net
} 設計
public class Test02 ...{ 對象
private Test01 t1 = new Test01(1); blog
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 修飾的成員呢?測試下面一段代碼:
[java] view plaincopy
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中方式,在執行時間上有前後嗎?爲了測試,我編寫了以下代碼:
[java] view plaincopy
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的成員變量呢,其實咱們在深刻分析一個類的內部初始化後,對於存在父類的類的初始化其實原理都同樣,具體如下面的代碼爲例:
[java] view plaincopy
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();
初始化時調用父類的構造方法,是執行父類的原方法,仍是執行子類中被重寫的方法呢?
[java] view plaincopy
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 ----
下面須要說明的一點也是相當重要的一點:那就是成員變量的初始化和非static初始化塊之間的執行順序是按照他們出現的前後順序來執行的
[java] view plaincopy
public class Test04
{
//下面的這兩行代碼放置的順序,跟執行結果是有關係的
private String t1 = test();
{
System.out.println("初始化快!");
}
//上面的這兩行代碼放置的順序,跟執行結果是有關係的
private String test(){
System.out.println("實例變量的執行過程");
return "test";
}
public Test04()
{
System.out.println("構造方法!");
}
public static void main(String[] args)
{
Test04 t2 = new Test04();
}
}
可是要注意: 若是初始化塊在變量定義以前,初始化塊調用變量會報錯。
總結初始化塊
1.初始化塊在構造方法調用前執行
2.初始化塊調用的時機與位置有關
若是放在類的開頭,對變量初始化會出錯,由於變量尚未定義,
所以 初始化塊放在類的末尾,變量的聲明定義放在類首。