例子
Talk is cheap, Show you the code!
public class ParentClass {
static int parentStaticField = 1;
final static int parentFinalStaticField = 2;
int parentSubField = 3;
static {
System.out.println("parent static!");
System.out.println("SubStatic field:" + SubClass.subStaticField);
System.out.println("SubFinalStatic field:" + SubClass.subFinalStaticField);
System.out.println("ParentStatic field:" + parentStaticField);
System.out.println("ParentFinalStatic field:" + parentFinalStaticField);
}
{
System.out.println("parent not static!");
System.out.println("SubStatic field:" + SubClass.subStaticField);
System.out.println("SubFinalStatic field:" + SubClass.subFinalStaticField);
System.out.println("ParentStatic field:" + parentStaticField);
System.out.println("ParentFinalStatic field:" + parentFinalStaticField);
System.out.println("Parent field:" + parentSubField);
}
public ParentClass() {
System.out.println("parent construct!");
}
}
public class SubClass extends ParentClass {
static int subStaticField = 1;
final static int subFinalStaticField = 2;
int subField = 3;
static {
System.out.println("Sub static!");
System.out.println("SubStatic field:" + SubClass.subStaticField);
System.out.println("SubFinalStatic field:" + SubClass.subFinalStaticField);
}
{
System.out.println("Sub no static!");
System.out.println("SubStatic field:" + SubClass.subStaticField);
System.out.println("SubFinalStatic field:" + SubClass.subFinalStaticField);
}
public SubClass() {
System.out.println("Sub Construct!");
}
public static void main(String[] args) {
new SubClass();
}
}
輸出:
parent static! --- 父類靜態代碼塊
SubStatic field:0
SubFinalStatic field:2
ParentStatic field:1 --- 父類靜態代碼塊調用之前靜態屬性已經初始化
ParentFinalStatic field:2
Sub static! --- 子類靜態代碼塊
SubStatic field:1
SubFinalStatic field:2
parent not static! --- 父類非靜態代碼塊
SubStatic field:1
SubFinalStatic field:2
ParentStatic field:1
ParentFinalStatic field:2
Parent field:3
parent construct! --- 父類構造函數
Sub no static! --- 子類非靜態代碼塊
SubStatic field:1
SubFinalStatic field:2
Sub Construct! --- 子類構造函數
從上面的例子總結類初始化的時候代碼的執行順序如下:
父類和子類的
final static屬性初始化 ---》 父類的static屬性初始化---》父類的static代碼塊---》子類的
static屬性初始化---》子類的static代碼塊 ---》父類的非靜態屬性 ---》父類的非靜態代碼塊---》父類的構造函數 ---》 子類的非靜態屬性---》 子類的非靜態代碼塊---》子類的構造含不管父類還是子類,靜態還是非靜態,屬性的初始化時間在代碼塊執行之前
static final修飾的屬性,不管是父類還是子類,在類加載的准備階段就已經初始化了,他要優先於任何代碼塊和屬性的初始化
細說
類的加載階段
1、加載
由類加載器負責根據一個類的全限定名來讀取此類的二進制字節流到JVM內部,並存儲在運行時內存區的方法區,然后將其轉換為一個與目標類型對應的java.lang.Class對象實例
2、驗證
格式驗證:驗證是否符合class文件規范
語義驗證:檢查一個被標記為final的類型是否包含子類;檢查一個類中的final方法是否被子類進行重寫;
確保父類和子類之間沒有不兼容的一些方法聲明(比如方法簽名相同,但方法的返回值不同)
操作驗證:在操作數棧中的數據必須進行正確的操作,對常量池中的各種符號引用執行驗證(通常在解析階段執行,檢查是否可以通過符號引用中描述的全限定名定位到指定類型上,以及類成員信息的訪問修飾符是否允許訪問等)
3、准備
為類中的所有靜態變量分配內存空間,並為其設置一個初始值(由於還沒有產生對象,實例變量不在此操作范圍內)
被final修飾的static變量(常量),會直接賦值;
4、解析
將常量池中的符號引用轉為直接引用(得到類或者字段、方法在內存中的指針或者偏移量,以便直接調用該方法),這個可以在初始化之后再執行。
解析需要靜態綁定的內容。 // 所有不會被重寫的方法和域都會被靜態綁定
以上2、3、4三個階段又合稱為鏈接階段,鏈接階段要做的是將加載到JVM中的二進制字節流的類數據信息合並到JVM的運行時狀態中。
5、初始化(先父后子)
為靜態變量賦值
執行static代碼塊
注意:static代碼塊只有jvm能夠調用
如果是多線程需要同時初始化一個類,僅僅只能允許其中一個線程對其執行初始化操作,其余線程必須等待,只有在活動線程執行完對類的初始化操作之后,才會通知正在等待的其他線程。
因為子類存在對父類的依賴,所以類的加載順序是先加載父類后加載子類,初始化也一樣。不過,父類初始化時,子類靜態變量的值也有有的,是默認值。
最終,方法區會存儲當前類類信息,包括類的靜態變量、類初始化代碼(定義靜態變量時的賦值語句 和 靜態初始化代碼塊)、實例變量定義、實例初始化代碼(定義實例變量時的賦值語句實例代碼塊和構造方法)和實例方法,還有父類的類信息引用。
對象創建階段
1、在堆區分配對象需要的內存
分配的內存包括本類和父類的所有實例變量,但不包括任何靜態變量
2、對所有實例變量賦默認值
將方法區內對實例變量的定義拷貝一份到堆區,然后賦默認值
3、執行實例初始化代碼
初始化順序是先初始化父類再初始化子類,初始化時先執行實例代碼塊然后是構造方法
4、如果有類似於Child c = new Child()形式的c引用的話,在棧區定義Child類型引用變量c,然后將堆區對象的地址賦值給它
需要注意的是,每個子類對象持有父類對象的引用,可在內部通過super關鍵字來調用父類對象,但在外部不可訪問
補充:
通過實例引用調用實例方法的時候,先從方法區中對象的實際類型信息找,找不到的話再去父類類型信息中找。
如果繼承的層次比較深,要調用的方法位於比較上層的父類,則調用的效率是比較低的,因為每次調用都要經過很多次查找。這時候大多系統會采用一種稱為虛方法表的方法來優化調用的效率。
所謂虛方法表,就是在類加載的時候,為每個類創建一個表,這個表包括該類的對象所有動態綁定的方法及其地址,包括父類的方法,但一個方法只有一條記錄,子類重寫了父類方法后只會保留子類的。當通過對象動態綁定方法的時候,只需要查找這個表就可以了,而不需要挨個查找每個父類。
