這篇文章主要講解Java在創建對象的時候,初始化的順序。主要從以下幾個例子中講解: 繼承關係中初始化順序 初始化塊與構造器的順序 已經載入過的類的初始化順序 載入父類,會不會載入子類 創建子類對象會不會創建父類對象 例子1——繼承關係中初始化順序 先看簡單的情況,看下麵的例子: 其執行的結果如下: ...
這篇文章主要講解Java在創建對象的時候,初始化的順序。主要從以下幾個例子中講解:
- 繼承關係中初始化順序
- 初始化塊與構造器的順序
- 已經載入過的類的初始化順序
- 載入父類,會不會載入子類
- 創建子類對象會不會創建父類對象
例子1——繼承關係中初始化順序
先看簡單的情況,看下麵的例子:
public class Father {
public String fatherVar = "父類構造塊初始化";
public static int fatherStaticVar;
public int i;
static {
int i = 100;
System.out.println("父類靜態塊初始化,i的值為" + i);
System.out.println("父類靜態變數初始化,fatherStaticVar的值為" + fatherStaticVar);
}
{
System.out.println(fatherVar);
}
public Father(){
System.out.println("父類構造函數的初始化,i的值" + i);
}
}
public class Son extends Father {
public String sonVar = "子類構造塊初始化";
public static int sonStaticVar;
public int i;
static {
int i = 101;
System.out.println("子類靜態塊初始化,i的值為" + i);
System.out.println("子類靜態變數初始化,sonStaticVar的值為" + sonStaticVar);
}
{
System.out.println(sonVar);
}
public Son(){
super();
System.out.println("子類構造函數的初始化,i的值" + i);
}
public static void main(String[] args) {
new Son();
}
}
其執行的結果如下:
父類靜態塊初始化,i的值為100
父類靜態變數初始化,fatherStaticVar的值為0
子類靜態塊初始化,i的值為101
子類靜態變數初始化,sonStaticVar的值為0
父類構造塊初始化
父類構造函數的初始化,i的值0
子類構造塊初始化
子類構造函數的初始化,i的值0
按照結果,我們可以知道在有繼承的時候,雖然是創建一個Son對象,但是JVM發現Son對象的類還沒有裝載,而Son類又繼承自Father類,只有載入了Father類,才能載入Son類。於是載入Father類的時候,就會初始化一切靜態變數和靜態塊。所以上文結果中第一行和第二行是父類靜態變數和靜態塊初始化的結果,然後載入完Father類之後,又會載入Son類,同樣是初始化Son類的靜態塊和靜態變數,出現上文中第三行和第四行的結果。等這個2個類都載入完了,才開始創建Son對象,因為Son對象,顯示調用了Father類的構造器,所以先執行Father類的構造器,出現第五行和第六行的結果,等Father類構造器執行完了,才執行後續Son構造器的內容,所以最後出現了第七行和第八行的結果。
例子2——初始化塊與構造器的順序
在上面的例子中,有2個語句塊叫初始化塊。在上文的結果中是初始化塊的執行是先於構造器的,現在看一下把初始化塊的內容放到構造器下麵,會是什麼的結果
public class InitBlock {
public InitBlock(){
System.out.println("構造器在執行......");
}
{
System.out.println("初始化塊1在執行......");
}
{
System.out.println("初始化塊2在執行......");
}
public static void main(String[] args) {
new InitBlock();
}
}
結果如下:
初始化塊1在執行......
初始化塊2在執行......
構造器在執行......
很顯然,無論初始化塊寫在哪個地方,都是先於構造器執行的,但是初始化塊之間的順序是前面的先初始化,後面在初始化。
例子3——已經載入過的類的初始化順序
更改一下例子1中的main方法,改成如下:
public static void main(String[] args) {
new Father();
System.out.println("=============");
new Son();
}
結果如下:
父類靜態塊初始化,i的值為100
父類靜態變數初始化,fatherStaticVar的值為0
子類靜態塊初始化,i的值為101
子類靜態變數初始化,sonStaticVar的值為0
父類構造塊初始化
父類構造函數的初始化,i的值0
=============
父類構造塊初始化
父類構造函數的初始化,i的值0
子類構造塊初始化
子類構造函數的初始化,i的值0
結果很有意思,創建父類對象的時候,載入Father類,出現第一行和第二行的結果,但是這個竟然會還把子類的靜態變數和靜態塊初始化?這個原因,例子4在說。 最後執行父類的構造器創建父類對象。當再創建子類的時候,發現父類和子類已經載入過了,所以不會再載入Father和Son類,只會調用父類的構造器,再執行後續子類構造器的內容,創建子類。
例子4——載入父類,會不會載入子類
用一個嶄新的例子來看看上面,創建父類的時候,為什麼會列印出子類靜態初始化執行的結果。
public class StaticFather {
static{
System.out.println("父類靜態初始化塊");
}
}
public class StaticSon extends StaticFather{
static {
System.out.println("子類靜態初始化塊");
}
}
public class Test {
public static void main(String[] args) {
new StaticFather();
}
}
結果如下:
父類靜態初始化塊
這次就不會創建父類的時候,載入子類。例子3之所以出現這個原因 是因為main函數在子類中寫的,要執行main函數必須要載入子類。只會載入子類之前要先載入父類,因為不載入父類,只載入子類,怎麼讓子類調用父類的方法和變數。但是載入父類不會載入子類,反正父類也調用不了子類的方法。
例子5——創建子類對象會不會創建父類對象
做個實驗,看一下創建子類對象的時候,到底會不會創建一個父類對象,先說結論:不會。從道理上講,如果創建任何一個對象都要創建出一個他的父類對象的話,那麼整個JVM虛擬機都是Object對象。看下麵的實驗:
public class ObjectFather {
public void getInfo(){
System.out.println(getClass().toString());
}
}
public class ObjectSon extends ObjectFather{
public ObjectSon(){
super();
super.getInfo();
}
public static void main(String[] args) {
new ObjectSon();
}
}
結果如下:
class com.byhieg.init.ObjectSon
可以看出來,創建子類對象時那個父類的Class還是子類的,也就是說創建子類對象並沒有創建一個父類的對象,只是說調用了父類的構造器,對父類的屬性進行初始化,並且給子類提供了一個super指示器去調用父類中那些變數和方法。
更詳細的說,new一個對象實際上是通過一個new指令開闢一個空間,來存放對象。在new ObjectSon()
的時候,就只有一個new指令,只會開闢一個空間,所謂初始化父類等等,都是在這個空間中有一個特殊的區域來存放這些數據,而super關鍵字就是提供了訪問這個特殊區域的方法,通過super去訪問這個特殊區域。
還可以比較super和this的hashcode
來判斷,結果必然是兩者的hashcode是一致的。
總結
至此,Java初始化的講解到結束了,基本了覆蓋了絕大多數情況中的初始化。