Java反射機制(四)―番外篇,實例化方法深入
來源:程序員人生 發布時間:2014-10-11 08:00:00 閱讀次數:3080次
反射機制這幾篇博客寫下來發現涉及到Java類的加載機制,這部分的內容也比較獨立的一部分,因此單另一篇來寫。在JAVA中任何的類都是需要加載到JVM中才能運行的。之前Class Loader介紹了類的加載機制,那么這里要說的是不同加載方式之間的對比,好能對JAVA類的實例化過程有更深刻的體會。
new和Class.newInstance
我們說代碼里出現new關鍵字意味著對于可能變動的代碼,耦合過高了。遇到這種情況我們會用反射機制來去除new關鍵字,這在代理模式里我們見過了。實際上也就是用了Class.newInstance來代替。這說明這兩種方式都可以得到相同的對象實例,但是它們之間存在區別,耦合度不同。
實際上在理解上我們可以認為,Class.newInstanc方式來實例化對象是對new關鍵字的拆分成兩步了。因為,Class.newInstance的使用是有前提的,要保證類已經加載到JVM中,并且已經鏈接。看如下代碼:
<span style="font-family:FangSong_GB2312;font-size:18px;"><span style="font-family:FangSong_GB2312;">public static void main(String[] arg) throws ClassNotFoundException, InstantiationException, IllegalAccessException{
//從當前線程取得正在運行的加載器
ClassLoader cl=Thread.currentThread().getContextClassLoader();
cl.loadClass("com.zjj.ClassTest.Test"); //加載測試類到JVM
Class c2=cl.getClass(); //得到類的Class對象
c2.newInstance(); //實例化對象
}
}</span></span>
這里不用Class.forName來得到Class對象是為了保證類被加載了但是沒有被鏈接。 這段代碼看著貌似沒什么錯,編譯也沒有問題,但是運行的時候就出錯了。也就是說通過如上方法加載的類是沒有被鏈接的,因此newInstance方法無法執行。
前面說理解上可以簡單的認為是通過Class.Instance方式是new拆分的兩步,但是事實上new要比Class.Instance做的多。Class.Instance方法只能訪問無參數的構造函數,new則都可以訪問。建立一個有兩個構造函數的測試類,看客戶端調用代碼:
<span style="font-family:FangSong_GB2312;font-size:18px;"><span style="font-family:FangSong_GB2312;">public static void main(String[] arg) throws ClassNotFoundException, InstantiationException, IllegalAccessException{
Class c=Class.forName("com.zjj.ClassTest.Test");
c.newInstance();
new Test("ni");
}
}</span></span>
輸出結果為:
無參數的構造函數
帶參數的構造函數
如果在newInstance中傳入參數去調用帶參數的構造函數的話是會報錯的,無法通過編譯。相對來說newInstance是弱類型,new是強類型。
Class.forName和classLoad.loadClass
講這兩個的區別之前我們先要了解,JVM會執行靜態代碼段,要記住一個概念,靜態代碼是和class綁定的,class裝載成功就表示執行了靜態代碼了,以后也就不會再走這段靜態代碼了。 也就是說靜態代碼段是只會執行一次的,在類被加載的時候。另外我們還需要知道,類的加載過程分為裝載、連接、初始化。還有就是,JVM遇到類請求時它會先檢查內存中是否存在,如果不存在則去加載,存在則返回已存在的Class對象。
那么這兩個方法的區別就在于執行的這三個過程不一樣。forName有兩個函數(多態),三個參數時forName(String className, boolean initialize, ClassLoader loader)第二個參數為True時則類會鏈接,會初始化。為False時,如果原來不存在則一定不會連接和初始化,如果原來存在被連接的Class對象,則返回該對象但是依然不會初始化。單參數時,默認initialize是為True的。
loadClass也是多態loadClass(String name)單參數時, resolve=false。如果該類已經被該類裝載器所裝載,那么,返回這個已經被裝載的類型的Class的實例,否則,就用這個自定義的類裝載器來裝載這個class,這時不知道是否被連接。絕對不會被初始化!這時唯一可以保證的是,這個類被裝載了。但是不知道這個類是不是被連接和初始化了。
loadClass(String name, boolean resolve)resolve=true時,則保證已經裝載,而且已經連接了。 resolve=falses時,則僅僅是去裝載這個類,不關心是否連接了,所以此時可能被連接了,也可能沒有被連接。下面通過測試來驗證以上說的內容,代碼如下:
Test類:
<span style="font-family:FangSong_GB2312;font-size:18px;"><span style="font-family:FangSong_GB2312;">public class Test {
static {
System.out.println("靜態初始化");
}
public Test(){
System.out.println("無參數的構造函數");
}
public Test(String str){
System.out.println("帶參數的構造函數");
}
{
System.out.println("非靜態初始化");
}
}</span></span>
測試一:客戶端調用代碼
<span style="font-family:FangSong_GB2312;font-size:18px;"><span style="font-family:FangSong_GB2312;">public static void main(String[] arg) throws ClassNotFoundException, InstantiationException, IllegalAccessException{
Class c=Class.forName("com.zjj.ClassTest.Test");
}
}</span></span>
輸出結果為:靜態初始化
說明:Class.forName時類執行了裝載、連接、初始化三個步驟。
測試二:客戶端代碼改為
<span style="font-family:FangSong_GB2312;font-size:18px;"><span style="font-family:FangSong_GB2312;">public static void main(String[] arg) throws ClassNotFoundException, InstantiationException, IllegalAccessException{
ClassLoader cl=Thread.currentThread().getContextClassLoader();
Class c=Class.forName("com.zjj.ClassTest.Test", false, cl);
}
}</span></span>
輸出結果為:initialize=true時輸出,靜態初始化。為false時沒有輸出
說明:為true時類執行了裝載、連接、初始化三個步驟。為false時沒有初始化,為知是不是連接。
測試三:客戶端代碼改為
<span style="font-family:FangSong_GB2312;font-size:18px;"><span style="font-family:FangSong_GB2312;">public static void main(String[] arg) throws ClassNotFoundException, InstantiationException, IllegalAccessException{
ClassLoader cl=Thread.currentThread().getContextClassLoader();
Class c=Class.forName("com.zjj.ClassTest.Test", false, cl);
c.newInstance();
}
}</span></span>
輸出結果為:
靜態初始化
非靜態初始化
無參數的構造函數
說明:為了保證JVM中不存在之前加載過的類,特地清理了JVM內存。但是輸出結果不變,說明為false時執行了裝載和鏈接,否則newInstance是無法執行的(前面說過了newInstance的執行條件)。但是資料說可能還存在不連接的情況!!有待考證。
測試四:客戶端代碼改為
<span style="font-family:FangSong_GB2312;font-size:18px;"><span style="font-family:FangSong_GB2312;">public static void main(String[] arg) throws ClassNotFoundException, InstantiationException, IllegalAccessException{
Class c=Class.forName("com.zjj.ClassTest.Test");
ClassLoader cl=Thread.currentThread().getContextClassLoader();
Class c=Class.forName("com.zjj.ClassTest.Test", true, cl);
}
}</span></span>
輸出結果為:靜態初始化
說明:如果原來存在加載過的類,那么第二次執行加載請求時返回存在的。因為,靜態初始化只執行了一次。
測試五:客戶端代碼改為
<span style="font-family:FangSong_GB2312;font-size:18px;"><span style="font-family:FangSong_GB2312;">public static void main(String[] arg) throws ClassNotFoundException, InstantiationException, IllegalAccessException{
//從當前線程取得正在運行的加載器
ClassLoader cl=Thread.currentThread().getContextClassLoader();
cl.loadClass("com.zjj.ClassTest.Test"); //加載測試類到JVM
Class c2=cl.loadClass("com.zjj.ClassTest.Test").getClass(); //得到類的Class對象
c2.newInstance(); //實例化對象
}
}</span></span>
輸出結果:報錯
說明:此時loadClass方法加載到內存中的類是未連接的,當然不會初始化。因此也就沒有“靜態初始化”的輸出。
測試六:不知道為什么沒有發現代碼中的ClassLoader存在兩個參數的loadClass方法。
總結:至此方法對比結束,這篇博客主要是更細致的了解了JVM加載類的過程和不同方式之間的區別。其實際上只是封裝的程度不一樣,也就是方法的粒度的差別。當然,有一點內容還沒有通過自己的測試得到驗證,可能是我的方法不對或者是資料有問題。權且記下這個問題!下篇博客再見!
生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈