ava Reflection API
運用示例
圖
5
示範
圖
4
提過的每一個
Reflection API
,及其執行結果。程序中出現的
tName()
是個輔助函數,可將其第一自變量所代表的
“
Java class
完整路徑字符串
”
剝除路徑部分,留下
class
名稱,儲存到第二自變量所代表的一個
hashtable
去並返回(如果第二自變量爲
null
,就不儲存而只是返回)。
#001
Class
c = null;
#002 c =
Class.forName
(args[0]);
#003
#004
Package
p;
#005 p = c.
getPackage
();
#006
#007 if (p != null)
#008 System.out.println("package "+p.
getName
()+";");
執行結果(例):
package java.util;
圖
5-1
:找出
class
隸屬的
package
。其中的
c
將繼續沿用於以下各程序片段。
#001 ff = c.
getDeclaredFields
();
#002 for (int i = 0; i < ff.length; i++)
#003 x = tName(ff[i].getType().getName(), classRef);
#004
#005 cn = c.
getDeclaredConstructors
();
#006 for (int i = 0; i < cn.length; i++) {
#007 Class cx[] = cn[i].getParameterTypes();
#008 for (int j = 0; j < cx.length; j++)
#009 x = tName(cx[j].getName(), classRef);
#010 }
#011
#012 mm = c.
getDeclaredMethods
();
#013 for (int i = 0; i < mm.length; i++) {
#014 x = tName(mm[i].getReturnType().getName(), classRef);
#015 Class cx[] = mm[i].getParameterTypes();
#016 for (int j = 0; j < cx.length; j++)
#017 x = tName(cx[j].getName(), classRef);
#018 }
#019 classRef.remove(c.getName()); //
不必記錄自己(不需
import
自己)
執行結果(例):
import java.util.ListIterator;
import java.lang.Object;
import java.util.LinkedList$Entry;
import java.util.Collection;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
圖
5-2
:找出導入的
classes
,動作細節詳見內文說明。
#001 int mod = c.
getModifiers
();
#002 System.out.print(
Modifier.toString
(mod)); //
整個
modifier
#003
#004 if (
Modifier.isInterface
(mod))
#005 System.out.print(" "); //
關鍵詞
"interface"
已含於
modifier
#006 else
#007 System.out.print(" class "); //
關鍵詞
"class"
#008 System.out.print(tName(c.
getName
(), null)); //
class
名稱
執行結果(例):
public class LinkedList
圖
5-3
:找出
class
或
interface
的名稱,及其屬性(
modifiers
)。
#001
TypeVariable<Class>[]
tv;
#002 tv = c.
getTypeParameters
(); //
warning: unchecked conversion
#003 for (int i = 0; i < tv.length; i++) {
#004 x = tName(tv[i].getName(), null); //
例如
E,K,V...
#005 if (i == 0) //
第一個
#006 System.out.print("<" + x);
#007 else //
非第一個
#008 System.out.print("," + x);
#009 if (i == tv.length-1) //
最後一個
#010 System.out.println(">");
#011 }
執行結果(例):
public abstract interface Map
<K,V>
或
public class LinkedList
<E>
圖
5-4
:找出
parameterized types
的名稱
#001 Class supClass;
#002 supClass = c.
getSuperclass
();
#003 if (supClass != null) //
如果有
super class
#004 System.out.print(" extends" +
#005 tName(supClass.
getName
(),classRef));
執行結果(例):
public class LinkedList<E>
extends AbstractSequentialList,
圖
5-5
:找出
base class
。執行結果多出一個不該有的逗號於尾端。此非本處重點,爲簡化計,不多做處理。
#001 Class cc[];
#002 Class ctmp;
#003 //
找出所有被實現的
interfaces
#004 cc = c.
getInterfaces
();
#005 if (cc.length != 0)
#006 System.out.print(", /r/n" + " implements "); //
關鍵詞
#007 for (Class cite : cc) //JDK1.5
新式循環寫法
#008 System.out.print(tName(cite.
getName
(), null)+", ");
執行結果(例):
public class LinkedList<E>
extends AbstractSequentialList,
implements List, Queue, Cloneable, Serializable,
圖
5-6
:找出
implemented interfaces
。執行結果多出一個不該有的逗號於尾端。此非本處重點,爲簡化計,不多做處理。
#001 cc = c.
getDeclaredClasses
(); //
找出
inner classes
#002 for (Class cite : cc)
#003 System.out.println(tName(cite.
getName
(), null));
#004
#005 ctmp = c.
getDeclaringClass
(); //
找出
outer classes
#006 if (ctmp != null)
#007 System.out.println(ctmp.
getName
());
執行結果(例):
LinkedList$Entry
LinkedList$ListItr
圖
5-7
:找出
inner classes
和
outer class
#001
Constructor
cn[];
#002 cn = c.
getDeclaredConstructors
();
#003 for (int i = 0; i < cn.length; i++) {
#004 int md = cn[i].
getModifiers
();
#005 System.out.print(" " + Modifier.toString(md) + " " +
#006 cn[i].getName());
#007 Class cx[] = cn[i].
getParameterTypes
();
#008 System.out.print("(");
#009 for (int j = 0; j < cx.length; j++) {
#010 System.out.print(tName(cx[j].getName(), null));
#011 if (j < (cx.length - 1)) System.out.print(", ");
#012 }
#013 System.out.print(")");
#014 }
執行結果(例):
public java.util.LinkedList(Collection)
public java.util.LinkedList()
圖
5-8a
:找出所有
constructors
#004 System.out.println(cn[i].
toGenericString
());
執行結果(例):
public java.util.LinkedList(java.util.Collection<? extends E>)
public java.util.LinkedList()
圖
5-8b
:找出所有
constructors
。本例在
for
循環內使用
toGenericString()
,省事。
#001
Method
mm[];
#002 mm = c.
getDeclaredMethods
();
#003 for (int i = 0; i < mm.length; i++) {
#004 int md = mm[i].
getModifiers
();
#005 System.out.print(" "+Modifier.toString(md)+" "+
#006 tName(mm[i].
getReturnType
().getName(), null)+" "+
#007 mm[i].getName());
#008 Class cx[] = mm[i].
getParameterTypes
();
#009 System.out.print("(");
#010 for (int j = 0; j < cx.length; j++) {
#011 System.out.print(tName(cx[j].getName(), null));
#012 if (j < (cx.length - 1)) System.out.print(", ");
#013 }
#014 System.out.print(")");
#015 }
執行結果(例):
public Object get(int)
public int size()
圖
5-9a
:找出所有
methods
#004 System.out.println(mm[i].
toGenericString
());
public E java.util.LinkedList.get(int)
public int java.util.LinkedList.size()
圖
5-9b
:找出所有
methods
。本例在
for
循環內使用
toGenericString()
,省事。
#001
Field
ff[];
#002 ff = c.
getDeclaredFields
();
#003 for (int i = 0; i < ff.length; i++) {
#004 int md = ff[i].
getModifiers
();
#005 System.out.println(" "+Modifier.toString(md)+" "+
#006 tName(ff[i].getType().getName(), null) +" "+
#007 ff[i].getName()+";");
#008 }
執行結果(例):
private transient LinkedList$Entry header;
private transient int size;
圖
5-10a
:找出所有
fields
#004 System.out.println("G: " + ff[i].toGenericString());
private transient java.util.LinkedList.java.util.LinkedList$Entry<E>
??
java.util.LinkedList.header
private transient int java.util.LinkedList.size
圖
5-10b
:找出所有
fields
。本例在
for
循環內使用
toGenericString()
,省事。
找出
class
參用
(導入)
的所有
classes
沒有直接可用的
Reflection API
可以爲我們找出某個
class
參用的所有其它
classes
。要獲得這項信息,必須做苦工,一步一腳印逐一記錄。我們必須觀察所有
fields
的類型、所有
methods
(包括
constructors
)的參數類型和回返類型,剔除重複,留下唯一。這正是爲什麼
圖
5-2
程序代碼要爲
tName()
指定一個
hashtable
(而非一個
null
)做爲第二自變量的緣故:
hashtable
可爲我們儲存元素(本例爲字符串),又保證不重複。
本文討論至此,幾乎可以還原一個
class
的原貌(唯有
methods
和
ctors
的定義無法取得)。接下來討論
Reflection
的另三個動態性質:
(1)
運行時生成
instances
,
(2)
執
行期喚起
methods
,
(3)
運行時改動
fields
。
運行時生成
instances
欲生成對象實體,在
Reflection
動態機制中有兩種作法,一個針對“無自變量
ctor
”,
一個針對“帶參數
ctor
”
。
圖
6
是面對“無自變量
ctor
”的例子。如果欲調用的是“帶參數
ctor
“就比較麻煩些,
圖
7
是個例子,其中不再調用
Class
的
newInstance()
,而是調用
Constructor
的
newInstance()
。
圖
7
首先準備一個
Class[]
做爲
ctor
的參數類型(本例指定爲一個
double
和一個
int
),然後以此爲自變量調用
getConstructor()
,獲得一個專屬
ctor
。接下來再準備一個
Object[]
做爲
ctor
實參值(本例指定
3.14159
和
125
),調用上述專屬
ctor
的
newInstance()
。
#001 Class c = Class.forName("DynTest");
#002 Object obj = null;
#003 obj =
c.newInstance
(); //
不帶自變量
#004 System.out.println(obj);
圖
6
:動態生成“
Class object
所對應之
class
”的對象實體;無自變量。
#001 Class c = Class.forName("DynTest");
#002 Class[] pTypes = new Class[] { double.class, int.class };
#003 Constructor
ctor = c.getConstructor
(pTypes);
#004 //
指定
parameter list
,便可獲得特定之
ctor
#005
#006 Object obj = null;
#007 Object[] arg = new Object[] {3.14159, 125}; //
自變量
#008 obj =
ctor.newInstance
(arg);
#009 System.out.println(obj);
圖
7
:動態生成“
Class object
對應之
class
”的對象實體;自變量以
Object[]
表示。
運行時
調用
methods
這個動作和上述調用“帶參數之
ctor
”相當類似。首先準備一個
Class[]
做爲
ctor
的參數類型(本例指定其中一個是
String
,另一個是
Hashtable
),然後以此爲自變量調用
getMethod()
,獲得特定的
Method
object
。接下來準備一個
Object[]
放置自變量,然後調用上述所得之特定
Method
object
的
invoke()
,如
圖
8
。知道爲什麼索取
Method
object
時不需指定回返類型嗎?因爲
method overloading
機制要求
signature
(署名式)必須唯一,而回返類型並非
signature
的一個成份。換句話說,只要指定了
method
名稱和參數列,就一定指出了一個獨一無二的
method
。
#001 public String
func
(String s, Hashtable ht)
#002 {
#003
…
System.out.println("func invoked"); return s;
#004 }
#005 public static void main(String args[])
#006 {
#007 Class c = Class.forName("Test");
#008 Class ptypes[] = new Class[2];
#009 ptypes[0] = Class.forName("java.lang.String");
#010 ptypes[1] = Class.forName("java.util.Hashtable");
#011
Method
m = c.
getMethod
("
func
",ptypes);
#012 Test obj = new Test();
#013 Object args[] = new Object[2];
#014 arg[0] = new String("Hello,world");
#015 arg[1] = null;
#016 Object r = m.
invoke
(obj, arg);
#017 Integer rval = (String)r;
#018 System.out.println(rval);
#019 }
圖
8
:動態喚起
method
運行時變更
fields
內
容
與先前兩個動作相比,“變更
field
內容”輕鬆多了,因爲它不需要參數和自變量。首先調用
Class
的
getField()
並指定
field
名稱。獲得特定的
Field
object
之後便可直接調用
Field
的
get()
和
set()
,如
圖
9
。
#001 public class Test {
#002 public double
d
;
#003
#004 public static void main(String args[])
#005 {
#006 Class c = Class.forName("Test");
#007
Field f
= c.
getField
("
d
"); //
指定
field
名稱
#008 Test obj = new Test();
#009 System.out.println("d= " +
(Double)
f.get
(obj));
#010 f
.set
(obj, 12.34);
#011 System.out.println("d= " + obj.
d
);
#012 }
#013 }
圖
9
:動態變更
field
內容
Java
源碼改動辦法
先前我曾提到,原本想借由“改動
Java
標準庫源碼”來測知
Class
object
的生成,但由於其
ctor
原始設計爲
private
,也就是說不可能透過這個管道生成
Class
object
(而是由
class loader
負責生成),因此“在
ctor
中
打印出某種信息”的企圖也就失去了意義。
這裏我要談點題外話:如何修改
Java
標準庫源碼並讓它反應到我們的應用程序來。假設我想修改
java.lang.Class
,讓它在某些情況下打印某種信息。首先必須找出標準源碼!當你下載
JDK
套件並安裝妥當,你會發現
jdk150/src/java/lang
目錄(見
圖
10
)之中有
Class.java
,這就是我們此次行動的標準源碼。備份後加以修改,編譯獲得
Class.class
。接下來準備將
.class
搬移到
jdk150/jre/lib/endorsed
(見
圖
10
)。
這是一個十分特別的目錄,
class loader
將優先從該處讀取內含
classes
的
.jar
文件
——
成功的條件是
.jar
內的
classes
壓縮路徑必須和
Java
標準庫的路徑完全相同。爲此,我們可以將剛纔做出的
Class.class
先搬到一個爲此目的而刻意做出來的
/java/lang
目錄中,壓縮爲
foo.zip
(任意命名,唯需夾帶路徑
java/lang
),再將這個
foo.zip
搬到
jdk150/jre/lib/endorsed
並改名爲
foo.jar
。此後你的應用程序便會優先用上這裏的
java.lang.Class
。整個過程可寫成一個批處理文件(
batch file
),如
圖
11
,在
DOS Box
中使用。
圖
10
:
JDK1.5
安裝後的目錄組織。其中的
endorsed
是我新建。
del e:/java/lang/*.class //
清理乾淨
del c:/jdk150/jre/lib/endorsed/foo.jar //
清理乾淨
c:
cd c:/jdk150/src/java/lang
javac -Xlint:unchecked Class.java
//
編譯源碼
javac -Xlint:unchecked ClassLoader.java /
/
編譯另一個源碼(如有必要)
move *.class e:/java/lang //
搬移至刻意製造的目錄中
e:
cd e:/java/lang //
以下壓縮至適當目錄
pkzipc
-add -path=root
c:/jdk150/jre/lib/endorsed/foo.jar
*.class
cd e:/test //
進入測試目錄
javac -Xlint:unchecked Test.java //
編譯測試程序
java Test //
執行測試程序
圖
11
:一個可在
DOS Box
中使用的批處理文件(
batch file
),用以自動化
java.lang.Class
的修改動作。
Pkzipc(.exe)
是個命令列壓縮工具,
add
和
path
都是其命令。