我們知道一個類(class)要被使用必須經過裝載,連接,初始化這樣的過程。下面先對這三階段做一個簡單的描述,之後會結合一個簡單的例子來說明java中類的初始化過程。
在裝載階段,類裝載器(Bootstrap ClassLoader 或者用戶自定義的ClassLoader) 把編譯形成的class文件載入內存,創建類相關的Class對象,這個Class對象封裝了我們要使用的類的類型信息。
連接階段又可以分為三個子步驟:驗證、准備和解析。
驗證就是要確保java類型數據格式 的正確性,並適於JVM使用。
准備階段,JVM為靜態變量分配內存空間,並設置默認值,注意,這裡是設置默認值,比如說int型的變量會被賦予默認值0 。在這個階段,JVM可能還會為一些數據結構分配內存,目的 是提高運行程序的性能,比如說方法表。
解析過程就是在類型的常量池中尋找類、接口、字段和方法的符號引用,把這些符號引用替換成直接引用。這個階段可以被推遲到初始化之後,當程序運行的過程中真正使用某個符號引用的時候 再去解析它。
類會在首次被“主動使用”時執行初始化,為類(靜態)變量賦予正確的初始值。在Java代碼中,一個正確的初始值是通過類變量初始化語句或者靜態初始化塊給出的。而我們這裡所說的主動使用 包括:
1.創建類的實例
2.調用類的靜態方法
3.使用類的非常量靜態字段
4.調用Java API中的某些反射方法
5.初始化某個類的子類
6.含有main()方法的類啟動時
初始化一個類包括兩個步驟:
1、 如果類存在直接父類的話,且直接父類還沒有被初始化,則先初始化其直接父類
2、 如果類存在一個初始化方法,就執行此方法
注:初始化接口並不需要初始化它的父接口。
好,下面我們通過一些簡單的例子來重點看一下初始化的過程 。
好,先上代碼(超級簡單的代碼)。
packagekevin.demo; classBase{ inta; staticintb; staticintc=1; static{ b=2; c=3; System.out.println("Base:staticinitblockinvoked.."); } voiddisplay(){ System.out.println("Base:a="+a+",b="+b+",c="+c); } } classDerivedextendsBase{ intd; staticinte=4; static{ e=5; System.out.println("Derived:staticinitblockinvoked..."); } voiddisplay(){ super.display(); System.out.println("Derived:d="+d+",e="+e); } }好,這裡我們定義了一個簡單的基類Base,然後定義了一個類Derived繼承它。這個已經簡單到我不知道說什麼了,直接上main方法吧。
publicclassDemo{ publicstaticvoidmain(String[]args){ newDerived().display(); } }好,我們執行這個方法,看會輸出什麼。。上個圖看一下:
如圖所示,雖然我們調用的子類的構造函數創建對象 ,但是父類中的有關初始化的語句和靜態初始化塊也會被執行或調用,這正與前面所說的:如果一個類的直接父類還沒有被初始化,那麼 先初始化它的父類。而為什麼這些初始化的動作會執行,就是因為我們主動使用了Derived這個類。而我們上面列出了好幾個主動使用的情況,好下面我們修改一下main方法,看上面所言是否正確。
注:因為我們在這裡談論的主題是類的初始化,所以關於對象的初始化過程我們這裡暫不討論。
好,看下修改後的Demo:
publicclassDemo{ staticintf; static{ f=100; System.out.println("Demo:staticinitblockinvoked..andf="+f); } publicstaticvoidmain(String[]args){ System.out.println("maininvoked.."); }}