class 載入
為了妥善使用有限的資源,Java 在真正需要使用到 class 的時候才會將其載入,當每一個 class 被載入時,JVM 就會為其自動產生一個 Class object。
【註】每一個 class 載入僅會有一個 Class object。因此即使使用同一個 class 產生多次 object,也只會有一個與其對應 Class object。
每一個 object 都可以透過 getClass() 的方式取得 Class object,以下為一個簡單的範例:
public class ClassDemo {
public static void main(String args[]) {
String name = "godleon";
Class stringClass = name.getClass();
System.out.println("類別名稱:" + stringClass.getName());
System.out.println("是否為介面:" + stringClass.isInterface());
System.out.println("是否為基本型態:" + stringClass.isPrimitive());
System.out.println("是否為陣列物件:" + stringClass.isArray());
System.out.println("父類別名稱:" + stringClass.getSuperclass().getName());
}
}
而 Java 只有在真正要用到 class 的時候才會將其載入,而真正用到的時候是指以下情況:
- 使用 class 生成 object 時
- 使用者指定要載入 class (利用 Class.forName() 或是 ClassLoader.loadClass())
若僅是宣告並不會載入 class,以下用一段程式碼來測試:
TestClass.java
LoadClassTest.javapublic class TestClass {
static {
System.out.println("類別被載入");
}
}
public class LoadClassTest {
public static void main(String args[]) {
TestClass test = null; //class不會載入,因此不會顯示「類別被載入」
System.out.println("宣告 TestClass 參考名稱");
test = new TestClass(); //class被載入,顯示「類別被載入」
System.out.println("生成 TestClass 實例");
}
}
此外,由於 Java 支援 Run-Time Type Identification(RTTI,執行時期型別辨識),因此 Class 的訊息在編譯時期就會被加入 .class 檔案中;而執行時,JVM 則會在使用某 class 時,會先檢查相對應的 Class object 是否已經載入,如果沒有載入,則會尋找相對應的 .class 檔案載入。
而一個 class 在 JVM 中只會有一個 Class object,所以每個 object 都可以透過 getClass() 或是 .class 屬性取得 Class object。
之前有說過,在 Java 中任何東西皆為 object,因此 Array 也不例外,甚至如 primitive type、關鍵字 void 都有相對應的 Class object,以下用一段程式來說明:
Class 類別中的 forName 與 newInstance 方法public class ClassDemo2 {
public static void main(String args[]) {
System.out.println(boolean.class); //boolean
System.out.println(void.class); //void
int[] intAry = new int[10];
System.out.println(intAry.getClass().toString()); //class [I
double[] dblAry = new double[10];
System.out.println(dblAry.getClass().toString()); //class [D
}
}
若要動態載入類別,可以使用 Class 類別中的 forName() method,此為 static method,因此可以直接透過 Class 類別使用;另外,若要取得類別的 instance,可使用 Class 類別中的 newInstance() method,以下用依段程式來說明兩個 method 的使用方法:
而當使用 newInstance() 取得 instance 後,就可以使用 getClassLoader()、getConstructor()、getFields()、getMethods()、getModifiers()、getPackage() ..... 等方法,取得 Class 所產生的 instance 的相關資訊,而類別檢視器就是利用這樣子的方式所做出來的!import java.util.*;
public class ClassForNameDemo {
public static void main(String[] args) {
try {
//forName的使用
Class c = Class.forName(args[0]);
System.out.println("getName : " + c.getName());
System.out.println("isInterface : " + c.isInterface());
System.out.println("isPrimitive : " + c.isPrimitive());
System.out.println("isArray : " + c.isArray());
System.out.println("SuperClass : " + c.getCanonicalName());
//newInstance的使用
List lst = (List) c.newInstance();
for(int i = 0 ; i < 10 ; i++)
lst.add("element" + i);
for(Object o : lst.toArray())
System.out.println(o);
}
catch(Exception e) {
e.printStackTrace();
}
}
}
Class Loader
當 Java 需要使用到 Class 時,才會將 Class 載入;而載入 Class 的工作是由 Class Loader(類別載入器) 所負責。
每當 Java 程式啟動後,會有以下三種 Class Loader 進行載入 Class 的工作:
- Bootstrap Loader
由 C++ 開發,會去搜尋 JRE 目錄($JAVA_HOME/jre/)中的 class 以及 lib 資料夾中的 *.jar 檔,檢查是否有指定的 class 需要載入。
- ExtClassLoader
會去搜尋 JRE 目錄($JAVA_HOME/jre/)中的 lib/ext 資料夾,檢查其中的 class 與 *.jat 檔案,是否有指定的 class 需要載入。
- AppClassLoader
搜尋 classpath 中是否有指定的 class 需要載入。
其中 Bootstrap Loader 為 ExtClassLoader 的 Parent Class;而 ExtClassLoader 則是 AppClassLoader 的 Parent Class。
而從 JVM 啟動後,載入的順序為 Bootstrap Loader -> ExtClassLoader -> AppClassLoader。
而每個 class 所產生出來的 instance 都可以使用 getClassLoader() 知道負責載入 class 的是哪一個 class loader。
動態載入類別、產生物件、呼叫方法
接著在這個部分,要介紹的是如何動態的載入將已經寫好的 class,並產生物件來呼叫 class 中所包含的 method,以下用兩段程式說明:
Student.java
public class Student {
private String name;
private int score;
public Student() {
name = "N/A";
}
public Student(String name, int score) {
this.name = name;
this.score = score;
}
public void setName(String name) {
this.name = name;
}
public void setScore(int score) {
this.score = score;
}
public String getName() {
return name;
}
public int getScore() {
return score;
}
public void showData() {
System.out.println("Name : " + this.name);
System.out.println("Score : " + this.score);
}
}
newInstanceDemo.java
由上面的程式碼可以知道,若是有執行時期動態載入 class,並呼叫其 method 的需求,透過使用 reflection,可以很簡單達成目的。import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class newInstanceDemo {
public static void main(String[] args) {
Class c = null;
try {
c = Class.forName(args[0]);
/* (Begin)=============== 生成物件 ===============(Begin) */
//指定Constructor所使用的參數型態
Class[] oParam = new Class[2];
oParam[0] = String.class;
oParam[1] = Integer.TYPE;
//產生Constructor
Constructor constructor = c.getConstructor(oParam);
//指定參數的內容
Object[] paramObjs = new Object[2];
paramObjs[0] = "godleon";
paramObjs[1] = new Integer(90);
//透過Constructor產生物件
Object obj = constructor.newInstance(paramObjs);
System.out.println(obj);
/* (End)=============== 生成物件 ===============(End) */
/* (Begin)=============== 呼叫方法 ===============(Begin) */
//指定Method所使用的參數類型
Class[] mParam1 = {String.class}; //只有一個參數
//產生Method(指定method名稱與參數)
Method setName = c.getMethod("setName", mParam1);
//指定參數內容
Object[] mParamObjs1 = {"godleon"};
//呼叫方法
setName.invoke(obj, mParamObjs1); //呼叫setName方法
//指定Method所使用的參數類型
Class[] mParam2 = {Integer.TYPE};
//產生Method(指定method名稱與參數)
Method setScore = c.getMethod("setScore", mParam2);
//指定參數內容
Object[] mParamObjs2 = {new Integer(90)};
//呼叫方法
setScore.invoke(obj, mParamObjs2); //呼叫setScore方法
//產生Method(指定method名稱與參數)
Method showData = c.getMethod("showData", null);
//呼叫方法
showData.invoke(obj, null); //呼叫showData方法
/* (End)=============== 呼叫方法 ===============(End) */
}
catch(Exception e) {
e.printStackTrace();
}
}
}
另外也可以動態的修改 Field 的值,可透過 getField() method 來達成,使用方式跟上面的程式大同小異,而若是要瞭解詳細的使用方式,可以參考此篇文章。
最後,此篇筆記就是著重在瞭解 Java 如何在執行期間動態載入 class 並使用,未來有機會用到的時候再來繼續深入探討.....
It's reallly good nice article about the Java reflection...
回覆刪除pinos
真的寫的很棒,又學到一些知識,感謝你的教學文章
回覆刪除謝謝你的文章 很有用的解釋
回覆刪除寫得很詳盡~感謝分享
回覆刪除very nice!!
回覆刪除很棒的文章
執行應該加java.util.Stack
回覆刪除謝謝您的文章
回覆刪除站在巨人的肩膀上 讓我看得更遠 有更好的發展