概要
本文通過查看一個精心構造的類結構的運行輸出和使用javap工具查看實際生成的java字節碼(bytecode)向java程序員展示了一個類在運行時是如何構造生成的
關鍵字 java 構造 javap 字節碼 bytecode 按照java規范
一個類實例的構造過程是遵循以下順序的
如果構造方法(constructor
也有翻譯為構造器和構造函數的)是有參數的則進行參數綁定
內存分配將非靜態成員賦予初始值(原始類型的成員的值為規定值
例如int型為
float型為
f
boolean型為false
對象類型的初始值為null)
靜態成員是屬於類對象而非類實例
所以類實例的生成不進行靜態成員的構造或者初始化
後面將講述靜態成員的生成時間
如果構造方法中存在this()調用(可以是其它帶參數的this()調用)則執行之
執行完畢後進入第
步繼續執行
如果沒有this調用則進行下一步
執行顯式的super()調用(可以是其它帶參數的super()調用)或者隱式的super()調用(缺省構造方法)
此步驟又進入一個父類的構造過程並一直上推至Object對象的構造
執行類申明中的成員賦值和初始化塊
執行構造方法中的其它語句
現在來看看精心構造的一個實例
class Parent
{
int pm
;
int pm
=
;
int pm
=pmethod();
{
System
out
println(
Parent
s instance initialize block
);
}
public static int spm
=
;
static
{
System
out
println(
Parent
s static initialize block
);
}
Parent()
{
System
out
println(
Parent
s default constructor
);
}
static void staticmethod()
{
System
out
println(
Parent
s staticmethod
);
}
int pmethod()
{
System
out
println(
Parent
s method
);
return
;
}
}
class Child extends Parent
{
int cm
;
int cm
=
;
int cm
=cmethod();
Other co;
public static int scm
=
;
{
System
out
println(
Child
s instance initialize block
);
}
static
{
System
out
println(
Child
s static initialize block
);
}
Child()
{
co=new Other();
System
out
println(
Child
s default constructor
);
}
Child(int m)
{
this();
cm
=m;
System
out
println(
Child
s self
define constructor
);
}
static void staticmethod()
{
System
out
println(
Child
s staticmethod
);
}
int cmethod()
{
System
out
println(
Child
s method
);
return
;
}
}
class Other
{
int om
;
Other() {
System
out
println(
Other
s default constructor
);
}
}
public class InitializationTest
{
public static void main(String args[])
{
Child c;
System
out
println(
program start
);
System
out
println(Child
scm
);
c= new Child(
);
System
out
println(
program end
);
}
}
進入此文件所在的目錄
然後
編譯此文件
javac InitializationTest
java
運行此程序
java ?classpath
InitializationTest
得到的結果是
program start
Parent
s static initialize block
Child
s static initialize block
Parent
s method
Parent
s instance initialize block
Parent
s default constructor
Child
s method
Child
s instance initialize block
Other
s default constructor
Child
s default constructor
Child
s self
define constructor
program end
如果沒有看過上面的關於類的構造的說明
很容易讓人誤解為類的構造順序是如下的結果(忽略參數綁定
內存分配和非靜態成員的缺省值賦值)
完成父類的非靜態成員初始化賦值以及執行初始化塊(這個的先後順序取決於源文件中的書寫順序
可以將初始化塊置於成員聲明前
那麼先執行的將是初始化塊
將上面的代碼稍稍變動一下就可以驗證這一點
)
調用父類的構造方法完成父類構造
完成非靜態成員的初始化賦值以及執行初始化塊
調用構造方法完成對象的構造
執行構造方法體中的其它內容
如果根據以上java規范中給出的順序也可以合理的解釋程序的輸出結果
那麼如何親眼看到是規范中的順序而不是以上根據程序的輸出推斷的順序呢?
下面就使用JDK自帶的javap工具看看實際的順序
這個工具是一個根據編譯後的字節碼生成一份字節碼的助記符格式的文檔的工具
就像根據機器碼生成匯編代碼那樣
反編譯
javap
c
classpath
Child
輸出的結果是(已經經過標記
交替使用黑體和斜體表示要講解的每一塊)
Compiled from InitializationTest
java
class Child extends Parent {
int cm
;
int cm
;
int cm
;
Other co;
public static int scm
;
static {};
Child();
Child(int);
int cmethod();
static void staticmethod();
}
Method static {}
bipush
putstatic #
getstatic #
ldc #
invokevirtual #
return
Method Child()
aload_
invokespecial #
aload_
bipush
putfield #
aload_
aload_
invokevirtual #
putfield #
getstatic #
ldc #
invokevirtual #
aload_
new #
dup
invokespecial #
putfield #
getstatic #
ldc #
invokevirtual #
return
Method Child(int)
aload_
invokespecial #
aload_
iload_
putfield #
getstatic #
ldc #
invokevirtual #
return
Method int cmethod()
getstatic #
ldc #
invokevirtual #
iconst_
ireturn
Method void staticmethod()
getstatic #
ldc #
invokevirtual #
return
請仔細浏覽一下這個輸出並和源代碼比較一下
下面解釋如何根據這個輸出得到類實例的實際的構造順序在開始說明前先解釋一下輸出的語句的格式語句中最前面的一個數字是指令的偏移值這個我們在此可以不管第二項是指令助記符可以從字面上大致看出指令的意思
例如 getstatic 指令將一個靜態成員壓入一個稱為操作數堆棧(後續的指令就可以引用這個數據結構中的成員)的數據結構而 invokevirtual 指令是調用java虛擬機方法第三項是操作數(#號後面跟一個數字實際上是類的成員的標記)有些指令沒有這一項因為有些指令如同匯編指令中的某些指令一樣是不需要操作數的(可能是操作數是隱含的或者根本就不需要)這是java中的一個特色
如果你直接檢查字節碼你會看到成員信息沒有直接嵌入指令而是像所有由java類使用的常量那樣存儲在一個共享池中將成員信息存儲在一個常量池中可以減小字節碼指令的大小因為指令只需要存儲常量池中的一個索引而不是整個常量
需要說明的是常量池中的項目的順序是和編譯器相關的因此在你的環境中看到的可能和我上面給出的輸出不完全一樣第四項是對前面的操作數的說明實際的字節碼中也是沒有的根據這個你能很清楚的得到實際上使用的是哪個成員或者調用的是哪個方法這也是javap為我們提供的便利
說完上面這些你現在應該很容易看懂上面的結果和下面將要敘述的
From:http://tw.wingwit.com/Article/program/Java/hx/201311/25958.html