java 類的初始化
Java類的初始化
我們知道一個類(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、 如果類存在一個初始化方法,就執行此方法
注:初始化介面並不需要初始化它的父介面。
好,下面我們通過一些簡單的例子來重點看一下初始化的過程 。
好,先上程式碼(超級簡單的程式碼)。
package kevin.demo; class Base { int a; static int b; static int c = 1; static { b=2; c = 3; System.out.println("Base:static init block invoked.."); } void display() { System.out.println("Base:a=" + a + ",b=" + b + ",c=" + c); } } class Derived extends Base { int d; static int e = 4; static { e= 5; System.out.println("Derived:static init block invoked..."); } void display() { super.display(); System.out.println("Derived:d=" + d + ",e=" + e); } }
好,這裡我們定義了一個簡單的基類Base,然後定義了一個類Derived繼承它。這個已經簡單到我不知道說什麼了,直接上main方法吧。
public class Demo {
public static void main(String[] args) {
new Derived().display();
}
}
好,我們執行這個方法,看會輸出什麼。。上個圖看一下:
如圖所示,雖然我們呼叫的子類的建構函式建立物件 ,但是父類中的有關初始化的語句和靜態初始化塊也會被執行或呼叫,這正與前面所說的:如果一個類的直接父類還沒有被初始化,那麼 先初始化它的父類。而為什麼這些初始化的動作會執行,就是因為我們主動使用了Derived這個類。而我們上面列出了好幾個主動使用的情況,好下面我們修改一下main方法,看上面所言是否正確。
注:因為我們在這裡談論的主題是類的初始化,所以關於物件的初始化過程我們這裡暫不討論。
好,看下修改後的Demo:
public class Demo {
static int f;
static{
f=100;
System.out.println("Demo:static init block invoked..and f="+f);
}
public static void main(String[] args) {
System.out.println("maininvoked..");
}}
好,看執行結果:
好,我們可以看到,含有main函式的類會首先執行初始化。
public class Demo {
static int f;
static{
f=100;
System.out.println("Demo:static init block invoked..and f="+f);
}
public static void main(String[] args) {
System.out.println(Derived.e);
}
}
下面再看一下,使用Derived的靜態變數會不會引起初始化過程。。
果然,這也屬於主動使用的一種,所以類執行了初始化,並且這裡看的更加清楚 ,含有main方法的類首先被初始化,因為它是第一個被使用的。但要注意,這裡的靜態就是不能是final的,否則初始化不會執行。
我們在main方法中新增一個語句,如下
public static void main(String[] args) {
System.out.println(Derived.e);
new Derived().display();
}
好, 這下看一下這個執行結果,能過這個,我主要 是想證明,類的初始化是隻執行一次呢還是每次主動使用類時都執行。。
通過這個截圖我們可以看到,我們添加了一條語句 ,但輸出結果只添加了兩行,呼叫Derived類的例項的display()方法只是執行了方法中的語句 ,並沒有再執行初始化語句,因此我們可以總結出,一個類只有會被初始化一次,除非是它被重新裝載。
Last but not least,讓我們看一下靜態巢狀類的初始化情況,它是要被主動使用的時候才初始化呢,還是它的外圍類被主動使用的時候 它就會被初始化呢?
為了方便,我們就在Demo類中新增一個靜態巢狀類Inner,如下
public class Demo {
static int f;
static {
f = 100;
System.out.println("Demo:static init block invoked..and f=" + f);
}
static class Inner{
static int g ;
static{
g=666;
System.out.println("Inner:static init block invoked..and g="+g);
}
}
public static void main(String[] args) {
}
}
看下main執行的結果:
我們可以看到,Inner類的靜態初始化塊並沒有被執行,也就是說它並沒有被初始化。好,現在在main函式中加上下面這句再看下執行結果:
System.out.println(Inner.g);
看截圖:
這下,很明顯,Inner的初始化進行了,所以說,它與外圍類沒有必然的聯絡,外圍類的初始化並不能導致它被初始化, 要初始化靜態巢狀類必須主動使用它,而主動使用其外圍類並不等同於主動使用Static Nested Class.
好,上面就是我所知道的關於類的初始化的東西了,如果有錯誤的地方還請高手指點,我及時修改。。
By KevinJom
2011/11/14