【前言】
最早的計算機在它們的原生彙編語言裡程式設計,它本質上是反射,因為它是由定義程式設計指令作為資料。反射功能,如修改指令或分析它們是很平常的。程式設計遷移到更高層次的語言如C,這種實踐消失了,直到帶有反射特性的程式設計語言在它們的類型系統出現。
Brian Cantwell Smith在1982年的博士論文中介紹了程式設計語言的計算反射的這種概念,并且meta-circular解釋器的概念用作3-Lisp的一個組成部分。
是什麼
定義:是在計算機程式正運作是(run time) 可以通路、檢測和修改它本身狀态或行為的一種能力。(維基百科)
為什麼要使用反射
反射通常由需要檢查或修改在Java虛拟機中運作的應用程式的運作時行為的能力的程式使用。這是一個比較先進的功能,隻能由強調掌握語言基礎的開發人員使用。考慮到這一點,反思是一種強大的技術,可以使應用程式執行否則是不可能的操作。
-
可擴充性特征
應用程式可以通過使用其全限定名稱建立可擴充性對象的執行個體來使用外部使用者定義的類。
-
類浏覽器和可視化開發環境
類浏覽器需要能夠枚舉類的成員。可視化開發環境可以利用反映的類型資訊來幫助開發人員編寫正确的代碼。
-
調試器和測試工具
調試者需要能夠在課堂上檢查私人成員。測試線束可以利用反射系統地調用類上定義的可發現的集合API,以確定測試套件中的高水準代碼覆寫。
适用場景是什麼?
1).Java的反射機制在做基礎架構的時候非常有用,有一句話這麼說來着:反射機制是很多Java架構的基石。而一般應用層面很少用,不過這種東西,現在很多開源架構基本都已經給你封裝好了,自己基本用不着寫。典型的除了hibernate之外,還有spring也用到很多反射機制。經典的就是在xml檔案或者properties裡面寫好了配置,然後在Java類裡面解析xml或properties裡面的内容,得到一個字元串,然後用反射機制,根據這個字元串獲得某個類的Class執行個體,這樣就可以動态配置一些東西,不用每一次都要在代碼裡面去new或者做其他的事情,以後要改的話直接改配置檔案,代碼維護起來就很友善了,同時有時候要适應某些需求,Java類裡面不一定能直接調用另外的方法,這時候也可以通過反射機制來實作。
總的來說,自己寫的很少,具體什麼時候要用那要看需求,反射機制無非就是根據一個String來得到你要的實體對象,然後調用它原來的東西。但是如果是要自己寫架構的話,那就會用得比較多了。
2)當你做一個軟體可以安裝插件的功能,你連插件的類型名稱都不知道,你怎麼執行個體化這個對象呢?因為程式是支援插件的(第三方的),在開發的時候并不知道 。是以無法在代碼中 New出來 ,但反射可以,通過反射,動态加載程式集,然後讀出類,檢查标記之後再執行個體化對象,就可以獲得正确的類執行個體。
3)在編碼階段不知道那個類名,要在運作期從配置檔案讀取類名, 這時候就沒有辦法寫死new ClassName(),而必須用到反射才能建立這個對象.反射的目的就是為了擴充未知的應用。比如你寫了一個程式,這個程式定義了一些接口,隻要實作了這些接口的dll都可以作為插件來插入到這個程式中。那麼怎麼實作呢?就可以通過反射來實作。就是把dll加載進記憶體,然後通過反射的方式來調用dll中的方法。很多工廠模式就是使用的反射。
執行個體
要了解反射如何工作,請考慮這個簡單的例子:
import java.lang.reflect.*;
public class DumpMethods {
public static void main(String args[])
{
try {
Class c = Class.forName(args[]);
Method m[] = c.getDeclaredMethods();
for (int i = ; i < m.length; i++)
System.out.println(m[i].toString());
}
catch (Throwable e) {
System.err.println(e);
}
}
}
調用:
輸出為:
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.Method是表示單一類方法的類。
設定使用反射
反射類,如Method,在java.lang.reflect中找到。 使用這些類必須遵循三個步驟。 第一步是為要操作的類擷取一個java.lang.Class對象。 java.lang.Class用于表示正在運作的Java程式中的類和接口。
擷取Class對象的一種方式是:
Class c = Class.forName("java.lang.String");
擷取String的Class對象。 另一種方法是使用:
Class c = int.class;
or
Class c = Integer.TYPE;
擷取基本類型的類資訊。 後一種方法通路基本類型的包裝器的預定義TYPE字段(例如整數)。
第二步是調用getDeclaredMethods等方法來擷取該類聲明的所有方法的清單。
一旦這個資訊在手,那麼第三步就是使用反射API來操縱資訊。 例如,序列:
Class c = Class.forName("java.lang.String");
Method m[] = c.getDeclaredMethods(); System.out.println(m[].toString());
将顯示在String中聲明的第一個方法的文本表示。
在下面的例子中,将三個步驟結合起來,展示如何使用反射來解決具體應用。
模拟運算符的執行個體
一旦Class資訊在手,通常下一步是詢問有關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());
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()是。
找出一個類的方法
反思最有價值和最基本的使用之一是找出課堂中定義的方法。 為此,可以使用以下代碼:
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 = ; 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 = ; j < pvec.length; j++)
System.out.println("
param #" + j + " " + pvec[j]);
Class evec[] = m.getExceptionTypes();
for (int j = ; 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對象的清單,隻需顯示有關參數類型,異常類型和每種方法的傳回類型的資訊。 這些類型中的每一種,無論它們是基本類還是類類型,都由Class描述符表示。
程式的輸出是:
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
-----
擷取關于構造函數的資訊
類似的方法用于了解類的構造函數。 例如:
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 = ; 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 = ; j < pvec.length; j++)
System.out.println("param #"
+ j + " " + pvec[j]);
Class evec[] = ct.getExceptionTypes();
for (int j = ; 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
-----
找出類字段
還可以找出在類中定義的資料字段。 為此,可以使用以下代碼:
import java.lang.reflect.*;
public class field1 {
private double d;
public static final int i = ;
String s = "testing";
public static void main(String args[])
{
try {
Class cls = Class.forName("field1");
Field fieldlist[]
= cls.getDeclaredFields();
for (int i
= ; 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。 這是一個反映類,它表示在一個字段成員上找到的修飾符,例如“private int”。 修飾符本身由整數表示,Modifier.toString用于以“官方”聲明順序傳回字元串表示形式(例如“final”之前的“static”)。 程式的輸出是:
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)中定義的字段的資訊。
按名稱調用方法
到目前為止,所提供的例子都與擷取課堂資訊有關。 但是也可以以其他方式使用反射,例如調用指定名稱的方法。
要了解如何工作,請考慮以下示例:
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[];
partypes[] = Integer.TYPE;
partypes[] = Integer.TYPE;
Method meth = cls.getMethod(
"add", partypes);
method2 methobj = new method2();
Object arglist[] = new Object[];
arglist[] = new Integer();
arglist[] = new Integer();
Object retobj
= meth.invoke(methobj, arglist);
Integer retval = (Integer)retobj;
System.out.println(retval.intValue());
}
catch (Throwable e) {
System.err.println(e);
}
}
}
假設一個程式想要調用add方法,但直到執行時才知道。也就是說,方法的名稱在執行期間被指定(例如,這可以由JavaBeans開發環境完成)。上面的程式顯示了一個這樣做的方法。
getMethod用于在類中找到一個具有兩個整數參數類型并具有适當名稱的方法。一旦這個方法被發現并被捕獲到一個方法對象中,它将被調用适當類型的對象執行個體。要調用一個方法,必須構造參數清單,其中整數值37和47包含在整數對象中。傳回值(84)也被包裹在一個整數對象中。
建立新對象
因為調用構造函數等效于建立一個新的對象(最精确的是,建立一個新對象涉及記憶體配置設定和對象構造),是以沒有等同于構造函數的方法調用。是以最接近于前一個例子就是說:
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[];
partypes[] = Integer.TYPE;
partypes[] = Integer.TYPE;
Constructor ct
= cls.getConstructor(partypes);
Object arglist[] = new Object[];
arglist[] = new Integer();
arglist[] = new Integer();
Object retobj = ct.newInstance(arglist);
}
catch (Throwable e) {
System.err.println(e);
}
}
}
它找到一個構造函數來處理指定的參數類型并調用它,以建立對象的新執行個體。 這種方法的價值在于它純粹是動态的,在執行時,而不是在編譯時,使用構造函數查找和調用。
改變字段的值
反射的另一個用途是改變對象中資料字段的值。 它的值再次從反射的動态特性得出,其中可以通過執行程式中的名稱查找一個字段,然後改變其值。 這通過以下示例說明:
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, );
System.out.println("d = " + f2obj.d);
}
catch (Throwable e) {
System.err.println(e);
}
}
}
在此示例中,d字段的值設定為12.34。
使用數組
反思的最後一個用途是建立和操縱數組。 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, );
Array.set(arr, , "this is a test");
String s = (String)Array.get(arr, );
System.out.println(s);
}
catch (Throwable e) {
System.err.println(e);
}
}
}
此示例建立一個10長的字元串數組,然後将數組中的位置5設定為字元串值。 檢索并顯示該值。
更複雜的數組操作如下代碼所示:
import java.lang.reflect.*;
public class array2 {
public static void main(String args[])
{
int dims[] = new int[]{, , };
Object arr
= Array.newInstance(Integer.TYPE, dims);
Object arrobj = Array.get(arr, );
Class cls =
arrobj.getClass().getComponentType();
System.out.println(cls);
arrobj = Array.get(arrobj, );
Array.setInt(arrobj, , );
int arrcast[][][] = (int[][][])arr;
System.out.println(arrcast[][][]);
}
}
此示例建立一個5 x 10 x 15的int數組,然後繼續将數組中的位置[3] [5] [10]設定為值37.注意,多元數組實際上是數組數組 ,是以例如,在第一個Array.get之後,arrobj中的結果是一個10 x 15的數組。 這再次剝離,以獲得一個15長的陣列,該陣列中的第十個插槽是使用Array.setInt設定的。
請注意,建立的數組的類型是動态的,在編譯時不一定要知道。
總結
Java反射是有用的,因為它支援通過名稱動态檢索關于類和資料結構的資訊,并允許在執行的Java程式中進行操作。 此功能非常強大,在其他正常語言(如C,C ++,Fortran或Pascal)中沒有任何相似之處。