Java
?Reflection?(JAVA反射)????
Reflection?是?Java?程序開發語言的特征之一,它允許運行中的?Java?程序對自身進行檢查,或者說“自審”,并能直接操作程序的內部屬性。例如,使用它能獲得?Java?類中各成員的名稱并顯示出來。
Java?的這一能力在實際應用中也許用得不是很多,但是在其它的程序設計語言中根本就不存在這一特性。例如,Pascal、C?或者?C++?中就沒有辦法在程序中獲得函數定義相關的信息。
JavaBean?是?reflection?的實際應用之一,它能讓一些工具可視化的操作軟件組件。這些工具通過?reflection?動態的載入并取得?Java?組件(類)?的屬性。
1.?一個簡單的例子
考慮下面這個簡單的例子,讓我們看看?reflection?是如何工作的。
import?java.lang.reflect.*;
public?class?DumpMethods?{
???public?static?void?main(String?args[])?{
???????try?{
???????????Class?c?=?Class.forName(args[0]);
???????????Method?m[]?=?c.getDeclaredMethods();
???????????for?(int?i?=?0;?i?<?m.length;?i++)
???????????????System.out.println(m[i].toString());
???????}?catch?(Throwable?e)?{
???????????System.err.println(e);
???????}
???}
}
按如下語句執行:
java?DumpMethods?java.util.Stack
它的結果輸出為:
public?java.lang.Object?java.util.Stack.push(java.lang.Object)
public?synchronized?java.lang.Object?java.util.Stack.pop()
public?synchronized?java.lang.Object?java.util.Stack.peek()
public?boolean?java.util.Stack.empty()
public?synchronized?int?java.util.Stack.search(java.lang.Object)
這樣就列出了java.util.Stack?類的各方法名以及它們的限制符和返回類型。
這個程序使用?Class.forName?載入指定的類,然后調用?getDeclaredMethods?來獲取這個類中定義了的方法列表。java.lang.reflect.Methods?是用來描述某個類中單個方法的一個類。
2.開始使用?Reflection
用于?reflection?的類,如?Method,可以在?java.lang.relfect?包中找到。使用這些類的時候必須要遵循三個步驟:第一步是獲得你想操作的類的?java.lang.Class?對象。在運行中的?Java?程序中,用?java.lang.Class?類來描述類和接口等。
下面就是獲得一個?Class?對象的方法之一:
Class?c?=?Class.forName("java.lang.String");
這條語句得到一個?String?類的類對象。還有另一種方法,如下面的語句:
Class?c?=?int.class;
或者
Class?c?=?Integer.TYPE;
它們可獲得基本類型的類信息。其中后一種方法中訪問的是基本類型的封裝類?(如?Integer)?中預先定義好的?TYPE?字段。
第二步是調用諸如?getDeclaredMethods?的方法,以取得該類中定義的所有方法的列表。
一旦取得這個信息,就可以進行第三步了??使用?reflection?API?來操作這些信息,如下面這段代碼:
Class?c?=?Class.forName("java.lang.String");
Method?m[]?=?c.getDeclaredMethods();
System.out.println(m[0].toString());
它將以文本方式打印出?String?中定義的第一個方法的原型。
在下面的例子中,這三個步驟將為使用?reflection?處理特殊應用程序提供例證。
模擬?instanceof?操作符
得到類信息之后,通常下一個步驟就是解決關于?Class?對象的一些基本的問題。例如,Class.isInstance?方法可以用于模擬?instanceof?操作符:
class?A?{
}
public?class?instance1?{
???public?static?void?main(String?args[])?{
???????try?{
???????????Class?cls?=?Class.forName("A");
???????????boolean?b1?=?cls.isInstance(new?Integer(37));
???????????System.out.println(b1);
???????????boolean?b2?=?cls.isInstance(new?A());
???????????System.out.println(b2);
???????}?catch?(Throwable?e)?{
???????????System.err.println(e);
???????}
???}
}
在這個例子中創建了一個?A?類的?Class?對象,然后檢查一些對象是否是?A?的實例。Integer(37)?不是,但?new?A()?是。
3.找出類的方法
找出一個類中定義了些什么方法,這是一個非常有價值也非常基礎的?reflection?用法。下面的代碼就實現了這一用法:
import?java.lang.reflect.*;
public?class?method1?{
???private?int?f1(Object?p,?int?x)?throws?NullPointerException?{
???????if?(p?==?null)
???????????throw?new?NullPointerException();
???????return?x;
???}
???public?static?void?main(String?args[])?{
???????try?{
???????????Class?cls?=?Class.forName("method1");
???????????Method?methlist[]?=?cls.getDeclaredMethods();
???????????for?(int?i?=?0;?i?<?methlist.length;?i++)?{
???????????????Method?m?=?methlist[i];
???????????????System.out.println("name?=?"?+?m.getName());
???????????????System.out.println("decl?class?=?"?+?m.getDeclaringClass());
???????????????Class?pvec[]?=?m.getParameterTypes();
???????????????for?(int?j?=?0;?j?<?pvec.length;?j++)
???????????????????System.out.println("param?#"?+?j?+?"?"?+?pvec[j]);
???????????????Class?evec[]?=?m.getExceptionTypes();
???????????????for?(int?j?=?0;?j?<?evec.length;?j++)
???????????????????System.out.println("exc?#"?+?j?+?"?"?+?evec[j]);
???????????????System.out.println("return?type?=?"?+?m.getReturnType());
???????????????System.out.println("-----");
???????????}
???????}?catch?(Throwable?e)?{
???????????System.err.println(e);
???????}
???}
}
這個程序首先取得?method1?類的描述,然后調用?getDeclaredMethods?來獲取一系列的?Method?對象,它們分別描述了定義在類中的每一個方法,包括?public?方法、protected?方法、package?方法和?private?方法等。如果你在程序中使用?getMethods?來代替?getDeclaredMethods,你還能獲得繼承來的各個方法的信息。
取得了?Method?對象列表之后,要顯示這些方法的參數類型、異常類型和返回值類型等就不難了。這些類型是基本類型還是類類型,都可以由描述類的對象按順序給出。
輸出的結果如下:
name?=?f1
decl?class?=?class?method1
param?#0?class?java.lang.Object
param?#1?int
exc?#0?class?java.lang.NullPointerException
return?type?=?int
-----
name?=?main
decl?class?=?class?method1
param?#0?class?[Ljava.lang.String;
return?type?=?void
-----
4.獲取構造器信息
獲取類構造器的用法與上述獲取方法的用法類似,如:
import?java.lang.reflect.*;
public?class?constructor1?{
???public?constructor1()?{
???}
???protected?constructor1(int?i,?double?d)?{
???}
???public?static?void?main(String?args[])?{
???????try?{
???????????Class?cls?=?Class.forName("constructor1");
???????????Constructor?ctorlist[]?=?cls.getDeclaredConstructors();
???????????for?(int?i?=?0;?i?<?ctorlist.length;?i++)?{
???????????????Constructor?ct?=?ctorlist[i];
???????????????System.out.println("name?=?"?+?ct.getName());
???????????????System.out.println("decl?class?=?"?+?ct.getDeclaringClass());
???????????????Class?pvec[]?=?ct.getParameterTypes();
???????????????for?(int?j?=?0;?j?<?pvec.length;?j++)
???????????????????System.out.println("param?#"?+?j?+?"?"?+?pvec[j]);
???????????????Class?evec[]?=?ct.getExceptionTypes();
???????????????for?(int?j?=?0;?j?<?evec.length;?j++)
???????????????????System.out.println("exc?#"?+?j?+?"?"?+?evec[j]);
???????????????System.out.println("-----");
???????????}
???????}?catch?(Throwable?e)?{
???????????System.err.println(e);
???????}
???}
}
這個例子中沒能獲得返回類型的相關信息,那是因為構造器沒有返回類型。
這個程序運行的結果是:
name?=?constructor1
decl?class?=?class?constructor1
-----
name?=?constructor1
decl?class?=?class?constructor1
param?#0?int
param?#1?double
-----
5.獲取類的字段(域)
找出一個類中定義了哪些數據字段也是可能的,下面的代碼就在干這個事情:
import?java.lang.reflect.*;
public?class?field1?{
???private?double?d;
???public?static?final?int?i?=?37;
???String?s?=?"testing";
???public?static?void?main(String?args[])?{
???????try?{
???????????Class?cls?=?Class.forName("field1");
???????????Field?fieldlist[]?=?cls.getDeclaredFields();
???????????for?(int?i?=?0;?i?<?fieldlist.length;?i++)?{
???????????????Field?fld?=?fieldlist[i];
???????????????System.out.println("name?=?"?+?fld.getName());
???????????????System.out.println("decl?class?=?"?+?fld.getDeclaringClass());
???????????????System.out.println("type?=?"?+?fld.getType());
???????????????int?mod?=?fld.getModifiers();
???????????????System.out.println("modifiers?=?"?+?Modifier.toString(mod));
???????????????System.out.println("-----");
???????????}
???????}?catch?(Throwable?e)?{
???????????System.err.println(e);
???????}
???}
}
這個例子和前面那個例子非常相似。例中使用了一個新東西?Modifier,它也是一個?reflection?類,用來描述字段成員的修飾語,如“private?int”。這些修飾語自身由整數描述,而且使用?Modifier.toString?來返回以“官方”順序排列的字符串描述?(如“static”在“final”之前)。這個程序的輸出是:
name?=?d
decl?class?=?class?field1
type?=?double
modifiers?=?private
-----
name?=?i
decl?class?=?class?field1
type?=?int
modifiers?=?public?static?final
-----
name?=?s
decl?class?=?class?field1
type?=?class?java.lang.String
modifiers?=
-----
和獲取方法的情況一下,獲取字段的時候也可以只取得在當前類中申明了的字段信息?(getDeclaredFields),或者也可以取得父類中定義的字段?(getFields)?。
6.根據方法的名稱來執行方法
文本到這里,所舉的例子無一例外都與如何獲取類的信息有關。我們也可以用?reflection?來做一些其它的事情,比如執行一個指定了名稱的方法。下面的示例演示了這一操作:
import?java.lang.reflect.*;
public?class?method2?{
???public?int?add(int?a,?int?b)?{
???????return?a?+?b;
???}
???public?static?void?main(String?args[])?{
???????try?{
???????????Class?cls?=?Class.forName("method2");
???????????Class?partypes[]?=?new?Class[2];
???????????partypes[0]?=?Integer.TYPE;
???????????partypes[1]?=?Integer.TYPE;
???????????Method?meth?=?cls.getMethod("add",?partypes);
???????????method2?methobj?=?new?method2();
???????????Object?arglist[]?=?new?Object[2];
???????????arglist[0]?=?new?Integer(37);
???????????arglist[1]?=?new?Integer(47);
???????????Object?retobj?=?meth.invoke(methobj,?arglist);
???????????Integer?retval?=?(Integer)?retobj;
???????????System.out.println(retval.intvalue());
???????}?catch?(Throwable?e)?{
???????????System.err.println(e);
???????}
???}
}
假如一個程序在執行的某處的時候才知道需要執行某個方法,這個方法的名稱是在程序的運行過程中指定的?(例如,JavaBean?開發環境中就會做這樣的事),那么上面的程序演示了如何做到。
上例中,getMethod?用于查找一個具有兩個整型參數且名為?add?的方法。找到該方法并創建了相應的?Method?對象之后,在正確的對象實例中執行它。執行該方法的時候,需要提供一個參數列表,這在上例中是分別包裝了整數?37?和?47?的兩個?Integer?對象。執行方法的返回的同樣是一個?Integer?對象,它封裝了返回值?84。
7.創建新的對象
對于構造器,則不能像執行方法那樣進行,因為執行一個構造器就意味著創建了一個新的對象?(準確的說,創建一個對象的過程包括分配內存和構造對象)。所以,與上例最相似的例子如下:
import?java.lang.reflect.*;
public?class?constructor2?{
???public?constructor2()?{
???}
???public?constructor2(int?a,?int?b)?{
???????System.out.println("a?=?"?+?a?+?"?b?=?"?+?b);
???}
???public?static?void?main(String?args[])?{
???????try?{
???????????Class?cls?=?Class.forName("constructor2");
???????????Class?partypes[]?=?new?Class[2];
???????????partypes[0]?=?Integer.TYPE;
???????????partypes[1]?=?Integer.TYPE;
???????????Constructor?ct?=?cls.getConstructor(partypes);
???????????Object?arglist[]?=?new?Object[2];
???????????arglist[0]?=?new?Integer(37);
???????????arglist[1]?=?new?Integer(47);
???????????Object?retobj?=?ct.newInstance(arglist);
???????}?catch?(Throwable?e)?{
???????????System.err.println(e);
???????}
???}
}
根據指定的參數類型找到相應的構造函數并執行它,以創建一個新的對象實例。使用這種方法可以在程序運行時動態地創建對象,而不是在編譯的時候創建對象,這一點非常有價值。
8.改變字段(域)的值
reflection?的還有一個用處就是改變對象數據字段的值。reflection?可以從正在運行的程序中根據名稱找到對象的字段并改變它,下面的例子可以說明這一點:
import?java.lang.reflect.*;
public?class?field2?{
???public?double?d;
???public?static?void?main(String?args[])?{
???????try?{
???????????Class?cls?=?Class.forName("field2");
???????????Field?fld?=?cls.getField("d");
???????????field2?f2obj?=?new?field2();
???????????System.out.println("d?=?"?+?f2obj.d);
???????????fld.setDouble(f2obj,?12.34);
???????????System.out.println("d?=?"?+?f2obj.d);
???????}?catch?(Throwable?e)?{
???????????System.err.println(e);
???????}
???}
}
這個例子中,字段?d?的值被變為了?12.34。
9.使用數組
本文介紹的?reflection?的最后一種用法是創建的操作數組。數組在?Java?語言中是一種特殊的類類型,一個數組的引用可以賦給?Object?引用。觀察下面的例子看看數組是怎么工作的:
import?java.lang.reflect.*;
public?class?array1?{
???public?static?void?main(String?args[])?{
???????try?{
???????????Class?cls?=?Class.forName("java.lang.String");
???????????Object?arr?=?Array.newInstance(cls,?10);
???????????Array.set(arr,?5,?"this?is?a?test");
???????????String?s?=?(String)?Array.get(arr,?5);
???????????System.out.println(s);
???????}?catch?(Throwable?e)?{
???????????System.err.println(e);
???????}
???}
}
例中創建了?10?個單位長度的?String?數組,為第?5?個位置的字符串賦了值,最后將這個字符串從數組中取得并打印了出來。
下面這段代碼提供了一個更復雜的例子:
import?java.lang.reflect.*;
public?class?array2?{
???public?static?void?main(String?args[])?{
???????int?dims[]?=?new?int[]{5,?10,?15};
???????Object?arr?=?Array.newInstance(Integer.TYPE,?dims);
???????Object?arrobj?=?Array.get(arr,?3);
???????Class?cls?=?arrobj.getClass().getComponentType();
???????System.out.println(cls);
???????arrobj?=?Array.get(arrobj,?5);
???????Array.setInt(arrobj,?10,?37);
???????int?arrcast[][][]?=?(int[][][])?arr;
???????System.out.println(arrcast[3][5][10]);
???}
}
例中創建了一個?5?x?10?x?15?的整型數組,并為處于?[3][5][10]?的元素賦了值為?37。注意,多維數組實際上就是數組的數組,例如,第一個?Array.get?之后,arrobj?是一個?10?x?15?的數組。進而取得其中的一個元素,即長度為?15?的數組,并使用?Array.setInt?為它的第?10?個元素賦值。
注意創建數組時的類型是動態的,在編譯時并不知道其類型。
posted on 2007-09-25 00:06
-274°C 閱讀(25182)
評論(2) 編輯 收藏 所屬分類:
JAVA