java中的類是動態載入的,我們先看一下我們常用的類載入方式,先有一個感性的認識,才能進一步
深入討論,類載入無非就是下面三種方式。
class A{}
class B{}
class C{}
public class Loader{
public static void main(String[] args) throws Exception{
Class aa=A.class;
Class bb=Class.forName("B");
Class cc=ClassLoader.getSystemClassLoader().loadClass("C");
}
}
我們先看.class字面量方式,很多人可能不知道這種方式,因為這種用法不是一般java文法。
通過javap我們可以發現,這種方式的大致等價於定義了一個靜態成員變數
static Class class$0;(後面的編號是增長的)
你可以試圖再定義一個 static Class class$0,應該會收到一個編譯錯誤(重複定義)。Class aa=A.class;
就相當於
if(class$0==null){
try{
Class.forName("A");
}
cacth(ClassNotFoundException e){
throw new NoClassDefFoundError(e);
}
}
Class aa=class$0;可以很清楚的看到,這種類的字面量定義其實不是載入類的方式,而是被編譯器處理了,實質
上是使用了Class.forName方法,但是使用這種方式有一個很大的好處就是不用處理異常,因為
編譯器處理的時候如果找不到類會拋出一個NoClassDefFoundError。也許你覺得需要處理
ClassNotFoundException這種異常,事實上99%的情況下我們可以把這種異常認為是一個錯誤。
所以大部分情況我們使用這種方式會更簡潔。最常用的方式就是Class.forName方式了,這也是一個通用的上層調用。這個方法有兩個重載,
可能很多人都忽略了第二個方法。
public static Class forName(String name) throws ClassNotFoundException
public static Class forName(String name, boolean initialize,ClassLoader loader) throws ClassNotFoundException第二個方法後面多了兩個參數,第二個參數表示是否初始化,第三個參數為指定的類載入器。
在上面的例子中:
Class bb=Class.forName("B");等價於
Class bb=Class.forName("B",true,Loader.class.getClassLoader());
這裡要詳細說一下這個類的初始化這個參數,如果這個參數為false的話,
類中的static成員不會被初始化,static語句塊也不會被執行。
也就是類雖然被載入了,但是沒有被初始化,不過在第一次使用時仍然會初始化。
所以我們有時候會看到Class.forName("XXX").newInstance()這樣的語句,為什麼這裡要建立一個
不用的執行個體呢?不過是為了保證類被初始化(相容以前的系統)。
其實第二個方法是比較難用的,需要指定類載入器,如果不指定而且又沒有安裝安全管理器的化,
是無法載入類的,只要看一下具體的實現就明白了。最本質的方式當然是直接使用ClassLoader載入了,所有的類最終都是通過ClassLoader載入的,
Class cc=ClassLoader.getSystemClassLoader().loadClass("C");
這裡通過使用系統類別載入器來載入某個類,很直接的方式,但是很遺憾的是通過這種方式載入類,
類是沒有被初始化的(也就是初始化被延遲到真正使用的時候).不過我們也可以借鑒上面的經驗,載入
後執行個體化一個對象Class cc=ClassLoader.getSystemClassLoader().loadClass("C").newInstance()。
這裡使用了系統類別載入器,也是最常用的類載入器,從classpath中尋找要載入的類。
java中預設有三種類載入器:引導類載入器,擴充類載入器,系統類別載入器。
java中的類載入有著規範的階層,如果我們要瞭解類載入的過程,需要明確知道哪個類被誰
載入,某個類載入器載入了哪些類等等,就需要深入理解ClassLoader的本質。
以上只是類載入的表面的東西,我們還將討論深層次的東西。