JAVA中類文件加載是動態(tài)的。JVM指令是被封裝在了. class文件里面,而.class文件的加載過程是動態(tài)的,也就是說當我們用到的時候才會去加載,如果不用的話,就不會去加載我們的類。這里所說的用到包括兩種方式,第一種就是new一個對象的時候(這個時候要特別注意,當設計到多態(tài)的時候,就會有一點點變化,這時候編譯器會做一些優(yōu)化,這樣以來當加載的時候會提前加載設計到多態(tài)的類,關于這一點下面有個例子(example 1)來說明。另一種就是當一個類的靜態(tài)代碼被調用的時候。
java 代碼//example 1 // Zoo.java abstract class Animal { Animal(){ System.out.println("Animal constructor"); } } class Tiger extends Animal { Tiger(){ System.out.println("Tig constructor "); } } class Dog extends Animal { Dog(){ System.out.println("Dog Constructor "); } } public class Zoo { private Animal am; //Example 1.1 //private Dog am; Example 1.2 private Tiger tiger; Zoo(){ tiger = new Tiger(); am = new Dog(); } public static void main(String [] args){ System.out.println("new Zoo before"); Zoo z = new Zoo(); System.out.println("new Zoo after "); } }
當我們注釋掉Example.1.1行時,運行Example1.2行,結果如下:
Example 1.2
分析以上兩圖的運行結果我們可以看出:當我們將子類對象賦值給父類時,編譯器會做一點優(yōu)化,于是加載器在還沒有new 子類對象的時候已經加載了父類以及子類(example1.1結果),當不存在多態(tài)的時候,我們可以看到是當要new Dog()的時候才會加載Dog以及父類。無論何種方式,在new之前,類確實已經加載到了內存中。
JAVA為我們提供了兩種動態(tài)機制。第一種是隱式機制。其實new一個對象和調用類的靜態(tài)方法時,就是隱式機制在工作。第二種是顯示機制。顯示的機制又有兩種策略(第一種是用java.lang.Class的forName(String str)方法,第二種是用java.lang.ClassLoader的loadClass())。
第一種:利用forName方法
當我們查API文檔就會發(fā)現forName方法有兩種形式。分別如下: public static Class<?> forName(String className) throws ClassNotFoundException public static Class<?> forName(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException
先來說說第二種方法:第二個方法值得注意的就是第二個參數boolean initialize,如果我們把這個參數設置為false,那么當我們加載完類后就不會執(zhí)行靜態(tài)代碼和靜態(tài)的初始化動作。只有當我們new一個對象的時候才會初始化。而第三個參數是用來指明類的加載器的。
如果查看java.lang.Class類的源代碼,上述兩種方法最終都會調用Class類中的私有的native方法forName0(),此方法的聲明如下:
private static native Class forName0(String name, boolean init , ClassLoader loader) throws ClassNotFoundException;
所以當我們調用Class.forName(name )時,其實是在方法內部調用了: forName0(name, true, ClassLoader.getCallerClassLoader());
當我們調用Class.forName(name, initialize, loader )的時候,實際上此方法內部調用了: forName0(name, initialize, loader);
下面看一個例子,如果方法中第二個參數為false的情況:
java 代碼//example 2.1 // Zoo.java abstract class Animal { static { System.out.println("Animal static code block "); } Animal(){ System.out.println("Animal constructor"); } } class Tiger extends Animal { Tiger(){ System.out.println("Tig constructor "); } } class Dog extends Animal { Dog(){ System.out.println("Dog Constructor "); } } public class Zoo { public static void main(String [] args)throws Exception { System.out.println("new Zoo before"); Zoo z = new Zoo(); Class c = Class.forName("Dog",false,z.getClass().getClassLoader()); System.out.println("initilize before "); Animal dog = (Animal)c.newInstance(); System.out.println("new Zoo after "); } }
類加載完成后并沒有立即執(zhí)行靜態(tài)初始化代碼,而是到了實例化的時候才進行了靜態(tài)初始化。有時候我們會說靜態(tài)代碼是在類第一次被加載時執(zhí)行的,并且只執(zhí)行一次。其實這是對與new一個對象,第一次訪問類的靜態(tài)代碼以及第二個參數為true時而言的,對于動態(tài)的加載來說,如果forName方法的第二個參數設置為false,那么就是在實例化的時候才會執(zhí)行靜態(tài)初始化。當然默認情況下第二個參數是true.
第二種方法:利用Class對象獲取的ClassLoader裝載。
下面是一個簡單的例子:
java 代碼//Example 2.2 //Zoo.java abstract class Animal { static { System.out.println("Animal static code block "); } Animal(){ System.out.println("Animal constructor"); } } class Tiger extends Animal { Tiger(){ System.out.println("Tig constructor "); } } class Dog extends Animal { Dog(){ System.out.println("Dog Constructor "); } } public class Zoo { public static void main(String [] args)throws Exception { Class c = Zoo.class; ClassLoader loader = c.getClassLoader(); System.out.println("loader before"); Class dog = loader.loadClass("Dog"); System.out.println("instance before "); Animal an = (Animal)dog.newInstance(); } }
loader完成以后并沒有立即進行靜態(tài)代碼的執(zhí)行。只有當newInstance()的時候才執(zhí)行靜態(tài)初始化,這和把public static Class forName(String name, boolean initialize, ClassLoader loader)的第二個參數指定為false的情況完全一樣。其實每當我們寫完一個編譯單元以后就會得到一個.calss文件,這個文件中就包含了該類的Class對象。JVM就是利用這個class對象來進行動態(tài)裝載類的。
|