標籤:
以下內容來自周志明的《深入理解java虛擬機器》: 類初始化階段是類載入過程的最後一步,前面的類載入過程中,除了在載入階段使用者應用程式可以通過自訂類載入器參與之外,其餘動作完全由虛擬機器主導和控制。 到了初始化階段,才真正開始執行類中定義的Java程式碼(或者說是位元組碼)。
在準備階段,變數已經賦過一次系統要求的初始值,而在初始化階段,則根據程式員通過程式制定的主觀計划去初始化類變數和其他資源,或者可以從另外一個角度來表達:初始化階段是執行類構造器<clinit>()方法的過程。 我們在下文會講解<clinit>()方法是怎麼產生的,在這裡,我們先看一下<clinit>()方法執行過程中一些可能會影響程式運行行為的特點和細節,這部分相對更貼近於普通的程式開發人員?。
? 1. <clinit>()方法是由編譯器自動收集類中的所有類變數的賦值動作和靜態語句塊(static{}塊)中的語句合并產生的,編譯器收集的順序是由語句在源檔案中出現的順序所決定的,靜態語句塊中只能訪問到定義在靜態語句塊之前的變數,定義在它之後的變數,在前面的靜態語句塊可以賦值,但是不能訪問。
2. ?<clinit>()方法與類的建構函式(或者說執行個體構造器<init>()方法)不同,它不需要顯式地調用父類構造器,虛擬機器會保證在子類的<clinit>()方法執行之前,父類的<clinit>()方法已經執行完畢。因此在虛擬機器中第一個被執行的<clinit>()方法的類肯定是java.lang.Object。
? 3. 由於父類的<clinit>()方法先執行,也就意味著父類中定義的靜態語句塊要優先於子類的變數賦值操作。
public class SuperClass { public static int value = 123; static { System.out.println("父類初始化。"); } public SuperClass() { System.out.println("父類建構函式。"); }}public class SubClass extends SuperClass { static { System.out.println("子類初始化。"); } public SubClass() { System.out.println("子類建構函式。"); }}public static void main(String[] args) { SubClass subClass = new SubClass();}//結果 父類優先於子類初始化,父類的建構函式同樣優先於子類的建構函式,但是初始化優先於建構函式。父類初始化。子類初始化。父類建構函式。子類建構函式。
4. ?<clinit>()方法對於類或介面來說並不是必需的,如果一個類中沒有靜態語句塊,也沒有對變數的賦值操作,那麼編譯器可以不為這個類產生<clinit>()方法。
? 5. 介面中不能使用靜態語句塊,但仍然有變數初始化的賦值操作,因此介面與類一樣都會產生<clinit>()方法。但介面與類不同的是,執行介面的<clinit>()方法不需要先執行父介面的<clinit>()方法。只有當父介面中定義的變數使用時,父介面才會初始化。另外,介面的實作類別在初始化時也一樣不會執行介面的<clinit>()方法。
? 6.
虛擬機器會保證一個類的<clinit>()方法在多線程環境中被正確地加鎖、同步,如果多個線程同時去初始化一個類,那麼只會有一個線程去執行這個類的<clinit>()方法,其他線程都需要阻塞等待,直到活動線程執行<clinit>()方法完畢。如果在一個類的<clinit>()方法中有耗時很長的操作,就可能造成多個進程阻塞?,在實際應用中這種阻塞往往是很隱形。 --所以可以使用靜態內部類來實現單利模式。
[讀書筆記] java類初始化