Java 語言的反射機制
在Java運作時環境中,對于任意一個類,能否知道這個類有哪些屬性和方法?對于任意一個對象,能否調用它的任意一個方法?
答案是肯定的。這種動态擷取類的資訊以及動态調用對象的方法的功能來自于Java 語言的反射(Reflection)機制。
Java 反射機制主要提供了以下功能
在運作時判斷任意一個對象所屬的類。
在運作時構造任意一個類的對象。
在運作時判斷任意一個類所具有的成員變量和方法。
在運作時調用任意一個對象的方法
Reflection 是Java被視為動态(或準動态)語言的一個關鍵性質。
這個機制允許程式在運作時透過Reflection APIs取得任何一個已知名稱的class的内部資訊,
包括其modifiers(諸如public, static 等等)、superclass(例如Object)、實作之interfaces(例如Serializable),
也包括fields和methods的所有資訊,并可于運作時改變fields内容或調用methods
一般而言,動态語言定義是:“程式運作時,允許改變程式結構或變量類型,這種語言稱為動态語言”。
從這個觀點看,Perl,Python,Ruby是動态語言,C++,Java,C#不是動态語言
盡管在這樣的定義與分類下Java不是動态語言,它卻有着一個非常突出的動态相關機制:Reflection。
這個字的意思是“反射、映象、倒影”,用在Java身上指的是我們可以于運作時加載、探知、使用編譯期間完全未的classes。
換句話說,Java程式可以加載一個運作時才得知名稱的class,獲悉其完整構造(但不包括methods定義),并生成其對象實體、或對其fields設值、或喚起其methods。
這種“看透class”的能力(the ability of the program to examine itself)被稱introspection(内省、内觀、檢討)。
Reflection和introspection是常被并提的兩個術語
在JDK中,主要由以下類來實作Java反射機制,這些類都位于java.lang.reflect包中
§Class類:代表一個類。
§Field 類:代表類的成員變量(成員變量也稱為類的屬性)。
§Method類:代表類的方法。
§Constructor 類:代表類的構造方法。
§Array類:提供了動态建立數組,以及通路數組的元素的靜态方法
例程DumpMethods類示範了Reflection API的基本作用,它讀取指令行參數指定的類名,然後列印這個類所具有的方法資訊
public class DumpMethods {
public static void main(String[] args) throws Exception {
// 加載初始化指令行參數指定的類,每一個類初始化加載時都有一個對應的Class類,這個Class類可以獲得該類的所有方法
Class<?> classType=Class.forName(args[0]);
// 獲得類的所有方法
Method methods[]=classType.getDeclaredMethods();
for(int i=0;i<methods.length;i++){
System.out.println(methods[i].toString());
}
}
}
運作方法:右擊-->Run AS -->Run Configurations-->Java Applivation-->DumpMethods-->Arguments-->prgram arguments-->輸入一個類(如:java.lang.String)-->Apply-->Run.
例程ReflectTester 類進一步示範了Reflection API的基本使用方法。ReflectTester類有一個copy(Object object)方法,這個方法能夠建立一個和參數object 同樣類型的對象,然後把object對象中的所有屬性拷貝到建立的對象中,并将它傳回
這個例子隻能複制簡單的JavaBean,假定JavaBean 的每個屬性都有public 類型的getXXX()和setXXX()方法。
public class ReflectTester {
public Object copy(Object object) throws Exception {
// 獲得對象類型,獲得Object對象所對應的Class類
Class<?> classType = object.getClass();
// 将Object所屬的類的名字列印出來,getName()獲得類的完整名字
System.out.println("Class:" + classType.getName());
// getConstructors():獲得類的public類型的構造方法。
// getConstructor(Class[] parameterTypes):獲得類的特定構造方法,parameterTypes
// 參數指定構造方法的參數類型。
// 通過預設構造方法建立一個新的對象,Constructor類代表的object類的構造方法,newInstance():通過類的不帶參數的構造方法建立這個類的一個對象。
Object objectCopy = classType.getConstructor(new Class[] {})
.newInstance(new Object[] {});// 通過預設的構造方法來建立一個目前類的執行個體,先調用Class類的getConstructor()方法獲得一個Constructor
// 對象,它代表預設的構造方法,然後調用Constructor對象的newInstance()方法構造一個執行個體。
// getDeclaredFields()獲得對象的所有屬性,Field類代表object類的成員變量(成員變量也稱為類的屬性),如果是getFields()是獲得類的public類型的所有方法
Field fields[] = classType.getDeclaredFields();// Class類的getDeclaredFields()方法傳回類的所有屬性,包括public、protected、預設和private通路級别的屬性
// 獲得每個屬性相應的getXXX()和setXXX()方法,然後執行這些方法,把原來對象的屬性拷貝到新的對象中
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
// 得到field對象所對應的屬性的名字
String fieldName = field.getName();
// 将fieldName的第一個字母轉化為大寫賦給firstLetter.
String firstLetter = fieldName.substring(0, 1).toUpperCase();
// 獲得和屬性對應的getXXX()方法的名字,将"get"+大寫的首字母+剩下的字母(從1到最後一個字母)
String getMethodName = "get" + firstLetter + fieldName.substring(1);
// 獲得和屬性對應的setXXX()方法的名字
String setMethodName = "set" + firstLetter + fieldName.substring(1);
// getMethod()獲得類的public的類型的方法,getDeclaredMethod()獲得類的所有類型的方法
// 獲得和屬性對應的getXXX()方法,Method類代表object類的方法,getMethod(String name,
// Class[] parameterTypes):獲得類的特定方法,name參數指定方法的名字,parameterTypes
// 參數指定方法的參數類型。
Method getMethod = classType.getMethod(getMethodName,
new Class[] {});
// 獲得和屬性對應的setXXX()方法
Method setMethod = classType.getMethod(setMethodName,
new Class[] { field.getType() });// 傳回設定的field屬性的Class類型
// invoke調用原對象的getXXX()方法,第一個為參數的方法對象的名稱,每二個為參數的方法對象的參數清單
Object value = getMethod.invoke(object, new Object[] {});// 調用object對象的Get方法
System.out.println(fieldName + ":" + value);
// 調用拷貝對象objectCopy的setXXX()方法
setMethod.invoke(objectCopy, new Object[] { value });
return objectCopy;
Customer customer = new Customer("Tom", 21);
customer.setId(new Long(1));
Customer customerCopy = (Customer) new ReflectTester().copy(customer);
System.out.println("Copy information:" + customerCopy.getId() + " "
+ customerCopy.getName() + " " + customerCopy.getAge());
class Customer {
private Long id;
private String name;
private int age;
public Customer() {
public Customer(String name, int age) {
this.name = name;
this.age = age;
public Long getId() {
return id;
public void setId(Long id) {
this.id = id;
public String getName() {
return name;
public void setName(String name) {
public int getAge() {
return age;
public void setAge(int age) {
在例程InvokeTester類的main()方法中,運用反射機制調用一個InvokeTester對象的add()和echo()方法
public class InvokeTester {
public int add(int param1, int param2) {
return param1 + param2;
public String echo(String msg) {
return "echo: " + msg;
// public InvokeTester(String str){
// }
// 獲得InvorkeTester類所對應的Class對象
Class<?> classType = InvokeTester.class;
// 調用不帶參數的預設構造方法生成一個執行個體
Object invokeTester = classType.newInstance();
// Object invokeTester = classType.getConstructor(new
// Class[]{}).newInstance(new Object[]{});
// 調用InvokeTester對象的add()方法
Method addMethod = classType.getMethod("add", new Class[] { int.class,
int.class });
// invoke用于調用方法,Method類的invoke(Object obj,Object
// args[])方法接收的參數必須為對象,如果參數為基本類型資料,必須轉換為相應的包裝類型的對象。invoke()方法的傳回值總是對象,如果實際被調用的方法的傳回類型是基本類型資料,那麼invoke()方法會把它轉換為相應的包裝類型的對象,再将其傳回
// 盡管InvokeTester 類的add()方法的兩個參數以及傳回值都是int類型,調用add Method
// 對象的invoke()方法時,隻能傳遞Integer 類型的參數,并且invoke()方法的傳回類型也是Integer
// 類型,Integer 類是int 基本類型的包裝類
Object result = addMethod.invoke(invokeTester, new Object[] {
new Integer(100), new Integer(200) });
System.out.println((Integer) result);
// 調用InvokeTester對象的echo()方法
Method echoMethod = classType.getMethod("echo",
new Class[] { String.class });
result = echoMethod.invoke(invokeTester, new Object[] { "Hello" });
System.out.println((String) result);
java.lang.Array 類提供了動态建立和通路數組元素的各種靜态方法。例程
ArrayTester1 類的main()方法建立了一個長度為10 的字元串數組,接着把索引位置為5 的元素設為“hello”,然後再讀取索引位置為5 的元素的值
public class ArrayTester1
{
public static void main(String args[]) throws Exception
{
// classType為java.lang.String對應的Class類
Class<?> classType = Class.forName("java.lang.String");
// 建立一個長度為10的字元串數組,生成java.lang.String對應的一個數組,
Object array = Array.newInstance(classType, 10);
// 把array數組的索引位置為5的元素設為"hello"
Array.set(array, 5, "hello");
// 獲得array數組的索引位置為5的元素的值
String s = (String) Array.get(array, 5);
System.out.println(s);
}
例程ArrayTester2 類的main()方法建立了一個 5 x 10 x 15 的整型數組,并把索引位置為[3][5][10] 的元素的值為設37
public class ArrayTester2 {
public static void main(String args[]) {
int[] dims = new int[] { 5, 10, 15 };
// Integer.TYPE獲得Integer對應的Class對象,dims決定是數組的維數及長度
Object array = Array.newInstance(Integer.TYPE, dims);
// 傳回數組第一維索引為3的類型的一個對象,此時為一個二維數組,第一維為3所在的一個二維數組
Object arrayObj = Array.get(array, 3);
// 傳回該數組的元件類型,如果不為數組的話傳回NULL
Class<?> cls = arrayObj.getClass().getComponentType();
System.out.println(cls);
// ==》System.out.println(cls.toString());
// 将第二維為5加進去,此時arrayObj為一個一維數組,第一維為3,第二維為5所在的一個數組
arrayObj = Array.get(arrayObj, 5);
// 将一維數組的第十個元素設定為37
Array.setInt(arrayObj, 10, 37);
int arrayCast[][][] = (int[][][]) array;
System.out.println(arrayCast[3][5][10]);
衆所周知Java有個Object class,是所有Java classes的繼承根源,其内聲明了數個應該在所有Java class中被改寫的methods:hashCode()、equals()、clone()、toString()、getClass()等。其中getClass()傳回一個Class object。
Class class十分特殊。它和一般classes一樣繼承自Object,其實體用以表達Java程式運作時的classes和interfaces,也用來表達enum、array、primitive Java types
(boolean, byte, char, short, int, long, float, double)以及關鍵詞void。當一個class被加載,或當加載器(class loader)的defineClass()被JVM調用,JVM 便自動産生一個Class object。如果您想借由“修改Java标準庫源碼”來觀察Class object的實際生成時機(例如在Class的constructor内添加一個println()),不能夠!因為Class并沒有public constructor
Class是Reflection起源。針對任何您想探勘的class,唯有先為它産生一個Class object,接下來才能經由後者喚起為數十多個的Reflection APIs
Java允許我們從多種途徑為一個class生成對應的Class object
Class object 誕生管道
示例
運用getClass()
注:每個class都有此函數運用
String str="abc";
Class c1=str.getClass();//str所對應的Class類
運用Class.getSuperClass()
Button b=new Button;
Class c1=b.getClass();
Class c2=c1.getSuperClass();//c1的父類所對應的Class類
運用static method Class.forname()
(最常被使用)
Class c1=Class.forName("java.lang.String");//java.lang.String所對應的Class類
Class c2=Class.forName("java.awt.Button");
Class c3=Class.forName("java.util.LinkedList$Entry");
Class c4=Class.forName("I");
Class c5=Class.forName("[I");
運用.class文法
Class c1=String.class;//String所對應的Class類
Class c2=java.awt.Button.class;
Class c3=Main.InnerClass.class;
Class c4=int.class;
Class c5=int[].class;
運用primitive wrapper classes的TYPE的文法
Class c1=Boolean.TYPE;//Boolean所對應的Class類
Class c2=Byte.TYPE;
Class c3=Character.TYPE;
Class c4=Short.TYPE;
Class c5=Integer.TYPE;
Class c6=Long.TYPE;
Class c7=Float.TYPE;
Class c8=Double.TYPE;
Class c9=Void.TYPE;
欲生成對象實體,在Reflection 動态機制中有兩種作法,一個針對“無自變量ctor”,一個針對“帶參數ctor”。如果欲調用的是“帶參數ctor“就比較麻煩些,不再調用Class的newInstance(),而是調用Constructor 的newInstance()。首先準備一個Class[]做為ctor的參數類型(本例指定
為一個double和一個int),然後以此為自變量調用getConstructor(),獲得一個專屬ctor。接下來再準備一個Object[] 做為ctor實參值(本例指定3.14159和125),調用上述專屬ctor的newInstance()。
Class c=Class.forName("DynTest");
Object obj=null;
obj=c.newInstance();//不帶自變量
System.out.println(obj);
動态生成“Class object 所對應之class”的對象實體;無自變量。
Class[] pType=new Class[]{double.class,int.class};
Constructor ctor=c.getConstructor(pTypes);
//指定parameter list,便可獲得特定之ctor
Object[] arg=new Object[]{3.14159,125};//自變量
obj=ctor.newInstance(arg);
動态生成“Class Object對應之Class“的實體對象;自變量以Object[]表示
這個動作和上述調用“帶參數之ctor”相當類似。
首先準備一個Class[]做為參數類型(本例指定其中一個是String,另一個是Hashtable),
然後以此為自變量調用getMethod(),獲得特定的Method object。
接下來準備一個Object[]放置自變量,然後調用上述所得之特定Method object的invoke()。
為什麼獲得Method object時不需指定回返類型?
因為method overloading機制要求signature必須唯一,而回返類型并非signature的一個成份。
換句話說,隻要指定了method名稱和參數列,就一定指出了一個獨一無二的method。
public String func(String s,Hashtable ht){
...
System.out.printl("func invoked");
return s;
public static void main(String args[]){
Class c=Class.forName("Test");
Class ptypes[]=new Class[2];
ptypes[0]=Class.forName("java.lang.String");
ptypes[1]=Class.forName("java.util.Hashtable");
method m=c.getMethod("func",ptypes);
Test obj=new Test();
Object args[]=new Object[2];
arg[0]=new String("Hello World");
arg[1]=null;
Object r=m.invoke(obj,arg);
Integer rval=(String)r;
System.out.println(rval);
與先前兩個動作相比,“變更field内容”輕松多了,因為它不需要參數和自變量。
首先調用Class的getField()并指定field名稱。
獲得特定的Field object之後便可直接調用Field的get()和set()
public class Test{
public double d;
public static void main(String args[]){
Class c=Class.forName("Test");
Field f=c.getField("d");//指定field名稱
Test obj=new Test();
System.out.println("d="+(Double)f.get(obj));
f.set(obj,12.34);
System.out.println("d="+obj.d);
動态變更field内容。