1 Java的动态特性
Java的动态特性有两种,一是隐式的;另一种是显示的。隱式的(implicit)方法就是當程式設計師用到new 這個Java 關鍵字時,會讓類別載入器依需求載入您所需要的類別,這種方式使用了隱式的(implicit)方法。顯式的方法,又分成兩種方式,一種是藉由java.lang.Class 裡的forName()方法,另一種則是藉由java.lang.ClassLoader 裡的loadClass()方法。您可以任意選用其中一種方法。
2 隐式的动态特性
在执行java文件时,只有单独的变量声明是不会载入相应的类的,只有在用new生成实例时才载入
如示例所示:
public class Main
public static void main(String args[])
{
A a1 = new A() ;
B b1 ;
}
类A和B相同,如下:
public class A
{
public void print(“using A”);
}
编译后,可用java –verbose:class Main运行,察看输出结果。可以看到JVM只载入了A,而没有载入B.
另外,类的载入只在执行到new一个类时,才载入,如果没有执行到new语句,则不载入。
如://类Office
public class Office
{
public static void main(String[] args)
{
Word myword=null;
Excel myexcel=null;
if (args[0].equals("Word"))
{
myword = new Word();
myword.start();
}
if (args[0].equals("Excel"))
{
myexcel = new Excel();
myexcel.start();
}
}
}
//类Word和Excel基本相同,如下
public class Word
{
public void start()
{
System.out.println("using word");
}
}
在dos命令提示符下,输入java –verbose Office Excel可以看到JVM只载入Excel类,而不载入Word类。
3 显示的动态特性
3.1 java.lang.Class里的forName()方法
在上一个Office示例中,进行如下修改:
一 加入Assembly类
public interface Assembly
{
public void start();
}
二 让Word和Excel类实现该接口
public class Word implements Assembly
{
public void start()
{
System.out.println("using word");
}
}
三 Office 类如下所示
public class Office
{
public static void main(String[] args) throws Exception
{
java.lang.Class c = java.lang.Class.forName(args[0]);
Object o = c.newInstance();
Assembly a = (Assembly)o;
a.start();
}
}
在命令提示符下输入java –verbose Office Word 输出入下:
通过上图你可以看到,interface 如同class 一般,會由編譯器產生一個獨立的類別檔(.class),當類別載入器載入類別時,如果發現該類別繼承了其他類別,或是實作了其他介面,就會先載入代表該介面的類別檔,也會載入其父類別的類別檔,如果父類別也有其父類別,也會一併優先載入。換句話說,類別載入器會依繼承體系最上層的類別往下依序載入,直到所有的祖先類別都載入了,才輪到自己載入。
下面介绍一下 forName 函数, 如果您親自搜尋Java 2 SDK 說明檔內部對於Class 這個類別的說明,您可以發現其實有兩個forName()方法,一個是只有一個參數的(就是之前程式之中所使用的):
public static Class forName(String className)
另外一個是需要三個參數的:
public static Class forName(String name, boolean initialize,ClassLoader loader)
這兩個方法,最後都是連接到原生方法forName0(),其宣告如下:
private static native Class forName0(String name, boolean initialize, ClassLoader loader)
throws ClassNotFoundException;
只有一個參數的forName()方法,最後叫用的是:
forName0(className, true, ClassLoader.getCallerClassLoader());
而具有三個參數的forName()方法,最後叫用的是:
forName0(name, initialize, loader);
这里initialize参数指,在载入类之后是否进行初始化,对于该参数的作用可用如下示例察看:
类里的静态初始化块在类第一次被初始化时才被呼叫,且仅呼叫一次。在Word类里,加入静态初始化块
public class Word implements Assembly
{
static
{
System.out.println("word static initialization ");
}
public void start()
{
System.out.println("using word");
}
}
将类Office作如下改变:
public class Office
{
public static void main(String[] args) throws Exception
{
Office off= new Office();
System.out.println("类别准备载入");
java.lang.Class c = java.lang.Class.forName(args[0],true,off.getClass().getClassLoader());
System.out.println("类别准备实体化");
Object o = c.newInstance();
Object o2 = c.newInstance();
}
}
如果第二个参数为true 则输出入下
如果为false ,则输出入下:
可见,类里的静态初始化块仅在初始化时才执行,且不过初始化几次,它仅执行一次(这里有一个条件,那就是只有它是被同一个类别载入器多次载入时,才是这样,如果被不同的载入器,载入多次,则静态初始化块会执行多次)。
关于第三个参数请见下节介绍
3.2 直接使用类别载入器 java.lang.ClassLoader
在Java 之中,每個類別最後的老祖宗都是Object,而Object 裡有一個名為getClass()的方法,就是用來取得某特定實體所屬類別的參考,這個參考,指向的是一個名為Class 類別(Class.class) 的實體,您無法自行產生一個Class 類別的實體,因為它的建構式被宣告成private,這個Class 類別的實體是在類別檔(.class)第一次載入記憶體時就建立的,往後您在程式中產生任何該類別的實體,這些實體的內部都會有一個欄位記錄著這個Class 類別的所在位置。
基本上,我們可以把每個Class 類別的實體,當作是某個類別在記憶體中的代理人。每次我們需要
查詢該類別的資料(如其中的field、method 等)時,就可以請這個實體幫我們代勞。事實上,Java的Reflection 機制,就大量地利用Class 類別。去深入Class 類別的原始碼,我們可以發現Class類別的定義中大多數的方法都是原生方法(native method)。
在Java 之中,每個類別都是由某個類別載入器(ClassLoader 的實體)來載入,因此,Class 類別的實體中,都會有欄位記錄著載入它的ClassLoader 的實體(注意:如果該欄位是null,並不代表它不是由類別載入器所載入,而是代表這個類別由靴帶式載入器(bootstrap loader,也有人稱rootloader)所載入,只不過因為這個載入器並不是用Java 所寫成,是用C++写的,所以邏輯上沒有實體)。
系統裡同時存在多個ClassLoader 的實體,而且一個類別載入器不限於只能載入一個類別,類別載入器可以載入多個類別。所以,只要取得Class 類別實體的參考,就可以利用其getClassLoader()方法籃取得載入該類別之類別載入器的參考。getClassLoader()方法最後會呼叫原生方法getClassLoader0(),其宣告如下:private native ClassLoader getClassLoader0();
最後,取得了ClassLoader 的實體,我們就可以叫用其loadClass()方法幫我們載入我們想要的類别,因此上面的Office类可做如下修改:
public class Office
{
public static void main(String[] args) throws Exception
{
Office off= new Office();
System.out.println("类别准备载入");
ClassLoader loader = off.getClass().getClassLoader();
java.lang.Class c = loader.loadClass(args[0]);
System.out.println("类别准备实体化");
Object o = c.newInstance();
Object o2 = c.newInstance();
}
}
其输出结果同forName方法的第二个参数为false时相同。可见载入器载入类时只进行载入,不进行初始化。
获取ClassLoader还可以用如下的方法:
public class Office
{
public static void main(String[] args) throws Exception
{
java.lang.Class cb = Office.class;
System.out.println("类别准备载入");
ClassLoader loader = cb.getClassLoader();
java.lang.Class c = loader.loadClass(args[0]);
System.out.println("类别准备实体化");
Object o = c.newInstance();
Object o2 = c.newInstance();
}
}
在此之前,當我們談到使用類別載入器來載入類別時,都是使用既有的類別載入器來幫我們載
入我們所指定的類別。那麼,我們可以自己產生類別載入器來幫我們載入類別嗎? 答案是肯定的。
利用Java 本身提供的java.net.URLClassLoader 類別就可以做到。
public class Office
{
public static void main(String[] args) throws Exception
{
URL u = new URL("file:/d:/myapp/classload/");
URLClassLoader ucl = new URLClassLoader(new URL[]{u});
java.lang.Class c = ucl.loadClass(args[0]);
Assembly asm = (Assembly)c.newInstance();
asm.start();
}
}
在這個範例中,我們自己產生java.net.URLClassLoader 的實體來幫我們載入我們所需要的類別。但是載入前,我們必須告訴URLClassLoader 去哪個地方尋找我們所指定的類別才行,所以我們必須給它一個URL 類別所構成的陣列,代表我們希望它去搜尋的所有位置。URL 可以指向網際網路上的任何位置,也可以指向我們電腦裡的檔案系統(包含JAR 檔)。在上述範例中,我們希望URLClassLoader 到d:\my\lib\ 這個目錄下去尋找我們需要的類別, 所以指定的URL為”file:/d:/my/lib/