原文:
http://today.java.net/pub/a/today/2008/02/12/reflection-in-action.html
你曾經(jīng)為IDE會(huì)自動(dòng)的列舉出所有你所編寫的類的詳情,甚至連私有的字段和方法也“難逃魔掌”而感到驚訝嗎?此外,這些IDE居然還能夠識(shí)別那些并不提供源碼并壓縮成JAR文件的類。它們是怎么做到的?
這些都是因?yàn)榉瓷洹?br />
本文將通過逐步列舉一個(gè)類的內(nèi)容,來闡明反射是如何被用來“撬動(dòng)”編程的。同時(shí)逐步形成高級(jí)別的抽象。我們將會(huì)從一個(gè)十分簡(jiǎn)單的例子開始,并一步步地在一個(gè)程序中實(shí)施反射。
什么是反射?
反射是一種機(jī)制,它允許動(dòng)態(tài)發(fā)現(xiàn)和綁定類、方法、字段,以及所有其他的由語言所產(chǎn)生的元素。反射可以做的不僅僅是簡(jiǎn)單地列舉類、字段以及方法。通過反射,我們可以還能夠在需要時(shí)完成創(chuàng)建實(shí)例、調(diào)用方法以及訪問字段的工作。
大多數(shù)程序員曾使用過動(dòng)態(tài)類載入技術(shù)來載入他們的JDBC驅(qū)動(dòng)。這種載入方法類似于下面這一段載入JDBC驅(qū)動(dòng)實(shí)例的代碼片段:
Class.forName("com.mysql.jdbc.Driver").newInstance();
為何與何時(shí)使用反射?
反射提供了一個(gè)高級(jí)別的抽象,換句話說,反射允許我們?cè)诔绦蜻\(yùn)行時(shí)對(duì)手頭上的對(duì)象進(jìn)行檢查并運(yùn)行。舉個(gè)例子,想像一下,當(dāng)你在執(zhí)行那相同的任務(wù)時(shí)——如像上面的例子那樣在若干的對(duì)象中查找一個(gè)實(shí)例,你可以選擇為每一個(gè)不同的對(duì)象寫相同的代碼,也可以使用反射來完成這項(xiàng)任務(wù)。或許你已經(jīng)開始意識(shí)到了,反射可以減少近似的代碼的維護(hù)量。因?yàn)槭褂昧朔瓷洌愕膶?shí)例查找代碼將會(huì)對(duì)其他類起作用。我們稍后將會(huì)演示這個(gè)例子。我已經(jīng)將它加入到這篇文章里,以便向你展示我們?nèi)绾螐姆瓷渲械美?br />
動(dòng)態(tài)發(fā)現(xiàn)
下面我們以發(fā)現(xiàn)一個(gè)類的內(nèi)容并列出它的構(gòu)造子,字段,方法作為開始吧。這并不實(shí)用,但它能讓我們直觀地抓住反射的原理及其了解其API。
創(chuàng)建一個(gè)Product類,如下所示。所有我們的例子都將放到了相同的package里,叫ria。
package ria;
public class Product {
private String description;
private long id;
private String name;
private double price;
//省略若干Getter與Setter
}
創(chuàng)建好Product類后,我們下面繼續(xù)創(chuàng)建第二個(gè)類,叫ReflectionUtil。它將列舉出第一個(gè)類(Product)的詳情。或許你已經(jīng)預(yù)料到了,這個(gè)類會(huì)包含一些實(shí)用的方法,它們將完成這個(gè)程序所需要的反射功能。目前,這個(gè)類只會(huì)包含一個(gè)方法,describeInstance(Object),它需要一個(gè)類型為Object的參數(shù)。
類ReflectionUtil的代碼如下所示。
package ria;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectionUtil {
public static void describeInstance(Object object) {
Class<?> clazz = object.getClass();
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
Field[] fields = clazz.getDeclaredFields();
Method[] methods = clazz.getDeclaredMethods();
System.out.println("Description for class: " + clazz.getName());
System.out.println();
System.out.println("Summary");
System.out.println("-----------------------------------------");
System.out.println("Constructors: " + (constructors.length));
System.out.println("Fields: " + (fields.length));
System.out.println("Methods: " + (methods.length));
System.out.println();
System.out.println();
System.out.println("Details");
System.out.println("-----------------------------------------");
if (constructors.length > 0) {
System.out.println();
System.out.println("Constructors:");
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
}
}
if (fields.length > 0) {
System.out.println();
System.out.println("Fields:");
for (Field field : fields) {
System.out.println(field);
}
}
if (methods.length > 0) {
System.out.println();
System.out.println("Methods:");
for (Method method : methods) {
System.out.println(method);
}
}
}
}
Java包含了一組反射相關(guān)的類,它們打包進(jìn)了反射API(Reflection API)中。構(gòu)造子類(Constructor)、字段類(Field)以及方法類(Method)便是其中的一部分。如同眾所周知的Class類一樣,它們?cè)贘ava中被用來在程序中描述對(duì)象。為了描述一個(gè)對(duì)象,我們需要知道這個(gè)對(duì)象是由什么組成的。我們?nèi)绾伍_始呢?那就從類開始吧,它包含了我們所有的代碼。
Class<?> clazz = object.getClass();
注意這里的泛型聲明Class<?>。泛型,簡(jiǎn)單地說,就是通過限定給出的實(shí)例是某種類型的,從而提供類型安全(type-safe)的操作。我們的方法(describeInstance(Object))并不綁定到一個(gè)特定類型上,它被設(shè)計(jì)為對(duì)任意給出的對(duì)象都能正常運(yùn)行。因此,那無限制的通配符,<?>,將會(huì)被使用到。
Class類有一些方法,下面我們將集中于那些對(duì)我們有用的方法上。下面的代碼片段中列出了這些方法。
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
Field[] fields = clazz.getDeclaredFields();
Method[] methods = clazz.getDeclaredMethods();
上面的這些來自Class類的方法返回了一組構(gòu)造子、字段、方法,這是它們組成了這個(gè)對(duì)象。
注意那個(gè)Class類含有兩組getter方法:一組在它們的名字中包含了declared單詞,而另一組沒有。不同之處在于,getDeclaredMethods()會(huì)返回所有屬于這個(gè)類的方法,而getMethods()則只返回聲明為public的方法。這對(duì)于理解為何只有在這個(gè)類中聲明的方法才予以返回的原因同樣重要。繼承的方法是不會(huì)被檢索到的。
了解ReflectionUtil類并沒有對(duì)一個(gè)關(guān)于Product類的引用十分重要。我們需要另一個(gè)類來創(chuàng)建一個(gè)Product實(shí)例并打印出它的詳情。
package ria;
public class Main {
public static void main(String[] args) throws Exception {
Product product = new Product();
product.setId(300);
product.setName("My Java Product Name");
product.setDescription("My Java Product description
");
product.setPrice(10.10);
ReflectionUtil.describeInstance(product);
}
}
上面的這個(gè)類運(yùn)行后應(yīng)該能輸出下面的這段信息(或一些近似的信息):
Description for class: ria.Product
Summary
-----------------------------------------
Constructors: 1
Fields: 4
Methods: 8
Details
-----------------------------------------
Constructors:
public ria.Product()
Fields:
private java.lang.String ria.Product.description
private long ria.Product.id
private java.lang.String ria.Product.name
private double ria.Product.price
Methods:
public java.lang.String ria.Product.getName()
public long ria.Product.getId()
public void ria.Product.setName(java.lang.String)
public void ria.Product.setId(long)
public void ria.Product.setDescription(java.lang.String)
public void ria.Product.setPrice(double)
public java.lang.String ria.Product.getDescription()
public double ria.Product.getPrice()
為了讓這個(gè)方法更加有用,我們可以加入打印出這個(gè)實(shí)例中定義的字段的值的功能。Field類有一個(gè)叫g(shù)et(Object)的方法,它返回給定的實(shí)例的相應(yīng)字段的值。
現(xiàn)在就以我們的Procuct類來舉個(gè)例子吧。這個(gè)類有四個(gè)實(shí)例變量。由于獲取的值依賴于實(shí)例,因此不同的實(shí)例可能有不同的值。所以,必須向Field提供實(shí)例作為參數(shù)輸入,這樣我們才能夠獲取這個(gè)實(shí)例的對(duì)應(yīng)的字段的值。如下所示:
field.get(object)
這里的field是Field的一個(gè)實(shí)例,同時(shí)object是為一個(gè)任意Java類的實(shí)例。
在我們草率地開始增加代碼前,我們必須認(rèn)識(shí)到這么一個(gè)事實(shí),那就是類的字段的私有訪問性是可以修改的。如果我們調(diào)用一個(gè)標(biāo)記為private的字段的Field類的get(Object)方法,這樣會(huì)拋出一個(gè)異常。因此,我們需要在著手訪問那個(gè)字段的值前,調(diào)用這個(gè)Field類的方法setAccessible(boolean),并傳遞true作為參數(shù)進(jìn)去。
field.setAccessible(true);
現(xiàn)在,我們知道了所有獲取字段的值的相關(guān)小技巧了,我們可以在剛才那個(gè)
decribeInstance(Object)方法下面
接著增加如下的代碼了。
if (fields.length > 0) {
System.out.println();
System.out.println();
System.out.println("Fields' values");
System.out.println("-----------------------------------------");
for (Field field : fields) {
System.out.print(field.getName());
System.out.print(" = ");
try {
field.setAccessible(true);
System.out.println(field.get(object));
} catch (IllegalAccessException e) {
System.out.println("(Exception Thrown: " + e + ")");
}
}
}
為了給你顯示這段代碼的威力,我來創(chuàng)建一個(gè)
java.awt.Rectangle類的實(shí)例吧,用這段增強(qiáng)版的
describeInstance(Object)
代碼來打印出這個(gè)實(shí)例的
詳情。
Rectangle rectangle = new Rectangle(1, 2, 100, 200);
ReflectionUtil.describeInstance(rectangle);
上面的這個(gè)代碼片段應(yīng)該能輸出一些類似下面的這些信息。提示一下,由于顯示的信息過長(zhǎng),部分信息被省略掉了。
Description for class: java.awt.Rectangle
Summary
-----------------------------------------
Constructors: 7
Fields: 5
Methods: 39
Details
-----------------------------------------
Constructors:
public java.awt.Rectangle()
public java.awt.Rectangle(java.awt.Rectangle)
public java.awt.Rectangle(int,int,int,int)
public java.awt.Rectangle(int,int)
public java.awt.Rectangle(java.awt.Point,java.awt.Dimension)
public java.awt.Rectangle(java.awt.Point)
public java.awt.Rectangle(java.awt.Dimension)
Fields:
public int java.awt.Rectangle.x
public int java.awt.Rectangle.y
public int java.awt.Rectangle.width
public int java.awt.Rectangle.height
private static final long java.awt.Rectangle.serialVersionUID
Methods:
public void java.awt.Rectangle.add(int,int)
public void java.awt.Rectangle.add(java.awt.Point)
public void java.awt.Rectangle.add(java.awt.Rectangle)
public boolean java.awt.Rectangle.equals(java.lang.Object)
public java.lang.String java.awt.Rectangle.toString()
public boolean java.awt.Rectangle.contains(int,int,int,int)
public boolean java.awt.Rectangle.contains(java.awt.Rectangle)
public boolean java.awt.Rectangle.contains(int,int)
public boolean java.awt.Rectangle.contains(java.awt.Point)
public boolean java.awt.Rectangle.isEmpty()
public java.awt.Point java.awt.Rectangle.getLocation()
public java.awt.Dimension java.awt.Rectangle.getSize()
public void java.awt.Rectangle.setSize(java.awt.Dimension)
public void java.awt.Rectangle.setSize(int,int)
public void java.awt.Rectangle.resize(int,int)
private static native void java.awt.Rectangle.initIDs()
public void java.awt.Rectangle.grow(int,int)
public boolean java.awt.Rectangle.intersects(java.awt.Rectangle)
private static int java.awt.Rectangle.clip(double,boolean)
public java.awt.geom.Rectangle2D java.awt.Rectangle.createIntersection(java
.
public java.awt.geom.Rectangle2D java.awt.Rectangle.createUnion(java.awt.geo
public java.awt.Rectangle java.awt.Rectangle.getBounds()
public java.awt.geom.Rectangle2D java.awt.Rectangle.getBounds2D()
public double java.awt.Rectangle.getHeight()
public double java.awt.Rectangle.getWidth()
public double java.awt.Rectangle.getX()
public double java.awt.Rectangle.getY()
public boolean java.awt.Rectangle.inside(int,int)
public java.awt.Rectangle java.awt.Rectangle.intersection(java.awt.Rectangle)
public void java.awt.Rectangle.move(int,int)
public int java.awt.Rectangle.outcode(double,double)
public void java.awt.Rectangle.reshape(int,int,int,int)
public void java.awt.Rectangle.setBounds(int,int,int,int)
public void java.awt.Rectangle.setBounds(java.awt.Rectangle)
public void java.awt.Rectangle.setLocation(java.awt.Point)
public void java.awt.Rectangle.setLocation(int,int)
public void java.awt.Rectangle.setRect(double,double,double,double)
public void java.awt.Rectangle.translate(int,int)
public java.awt.Rectangle java.awt.Rectangle.union(java.awt.Rectangle)
Fields' values
-----------------------------------------
x = 1
y = 2
width = 100
height = 200
serialVersionUID = -4345857070255674764
創(chuàng)建一個(gè)新的使用反射的實(shí)例
反射可以用來創(chuàng)建一個(gè)對(duì)象的實(shí)例。關(guān)于動(dòng)態(tài)創(chuàng)建對(duì)象的實(shí)例有許多例子,如前面所說的動(dòng)態(tài)載入一個(gè)JDBC驅(qū)動(dòng)。更進(jìn)一步,我們可以使用構(gòu)造子(Constructor
)類來創(chuàng)建新實(shí)例,特別是那些實(shí)例化時(shí)需要參數(shù)的實(shí)例。將下面的兩個(gè)重載的方法加入到我們的ReflectionUtil中。
public static <T> T newInstance(Class<T> clazz)
throws IllegalArgumentException, SecurityException,
InstantiationException, IllegalAccessException,
InvocationTargetException, NoSuchMethodException {
return newInstance(clazz, new Class[0], new Object[0]);
}
public static <T> T newInstance(Class<T> clazz, Class<?>[] paramClazzes,
Object[] params) throws IllegalArgumentException,
SecurityException, InstantiationException, IllegalAccessException,
InvocationTargetException, NoSuchMethodException {
return clazz.getConstructor(paramClazzes).newInstance(params);
}
注意,方法newInstance(Object[])在構(gòu)造子參數(shù)不匹配的時(shí)候可能會(huì)拋出一個(gè)異常。被實(shí)例化的類必須包含一個(gè)給定簽名的構(gòu)造子。
第一個(gè)方法(newInstance(Class<T>))可以用來實(shí)例化任何一個(gè)擁有默認(rèn)構(gòu)造子的類的對(duì)象。否則可以使用第二個(gè)方法:傳遞參數(shù)的類型以及其值,當(dāng)匹配上對(duì)應(yīng)的構(gòu)造子后就會(huì)執(zhí)行相應(yīng)的實(shí)例化。舉個(gè)例子,類Rectangle就可以通過使用含有四個(gè)int類型的參數(shù)的構(gòu)造子來進(jìn)行實(shí)例化。使用的代碼如下所示:
Object[] params = { 1, 2, 100, 200 };
Class[] paramClazzes = { int.class, int.class, int.class, int.class };
Rectangle rectangle = ReflectionUtil.newInstance(
Rectangle.class, paramClazzes, params);
System.out.println(rectangle);
上面的這段代碼將產(chǎn)生如下的輸出。
java.awt.Rectangle[x=1,y=2,width=100,height=200]
通過反射改變字段的值
字段的值可以使用反射來進(jìn)行設(shè)置,方法類似于如何使用反射來讀取它們。需要注意的是在設(shè)置這些字段的值前要設(shè)置它們的可訪問性,不然就會(huì)拋出異常的。
field.setAccessible(true);
field.set(object, newValue);
我們可以十分容易地編寫出一個(gè)方法,用它來設(shè)置任何對(duì)象的值,就如同下面列出的例子一般。
public static void setFieldValue(Object object, String fieldName,
Object newValue) throws NoSuchFieldException,
IllegalArgumentException, IllegalAccessException {
Class<?> clazz = object.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, newValue);
}
這個(gè)方法有一個(gè)不足,它只能檢索出這個(gè)類定義的字段;繼承而來的字段并不包括在內(nèi)。這個(gè)問題可以通過下面的這個(gè)方法來得到解決,它將向這個(gè)對(duì)象的基類查詢相應(yīng)的Field類。
public static Field getDeclaredField(Object object, String name)
throws NoSuchFieldException {
Field field = null;
Class<?> clazz = object.getClass();
do {
try {
field = clazz.getDeclaredField(name);
} catch (Exception e) { }
} while (field == null & (clazz = clazz.getSuperclass()) != null);
if (field == null) {
throw new NoSuchFieldException();
}
return field;
}
這個(gè)方法將返回給定名字的Field對(duì)象,如果找到的話;否則,就會(huì)拋出一個(gè)異常以提示這個(gè)類以及它的基類都沒有這個(gè)字段。這個(gè)方法從給定的類開始查找,逐步向上地查找各個(gè)基類直到每一個(gè)Field都被遍歷過。當(dāng)然,也有可能沒有基類。
提示,所有的Java類都是繼承于Object類。正如你所認(rèn)識(shí)到的,Object類并不繼承自己。所以O(shè)bject類沒有基類。
將前文提到的方法setFieldValue(Object, String, Object)進(jìn)行修改一下,以滿足這種情況。修改如下黑體所示。
public static void setFieldValue(Object object, String fieldName,
Object newValue) throws IllegalArgumentException,
IllegalAccessException, NoSuchFieldException {
Field field = getDeclaredField(object, fieldName);
field.setAccessible(true);
field.set(object, newValue);
}
下面來創(chuàng)建另一個(gè)叫做Book的類,它泛化于前面所討論過的Product類。我們將在此應(yīng)用我們剛才所學(xué)習(xí)到的關(guān)于反射的知識(shí)。
package ria;
public class Book extends Product {
private String isbn;
//Getters and setters are omitted for shortness
}
現(xiàn)在我們用方法setFieldValue(Object, String, Object)來設(shè)置book的id值。
Book book = new Book();
ReflectionUtil.setFieldValue(book, "id", 1234L);
System.out.println(book.getId());
上面這段代碼將輸出1234。
通過反射調(diào)用方法
你或許已經(jīng)猜到了,調(diào)用方法更上面提到的創(chuàng)建實(shí)例以及訪問字段的方法是十分相似的。
就反射而言,所有的方法都需要參數(shù)并會(huì)返回一個(gè)值。這或許聽起來有點(diǎn)怪異,但事實(shí)就是這樣。好吧,來看看下面的這個(gè)方法:
public void doNothing(){
// This method doesn't do anything
}
這個(gè)方法會(huì)返回一個(gè)void類型的值,同時(shí)它有一個(gè)空的參數(shù)列表。這個(gè)方法能夠通過下面的方式,用反射來進(jìn)行調(diào)用。
Class<?> clazz = object.getClass();
Method method = Clazz.getDeclaredMethod("doNothing");
method.invoke(object, new Object[0]);
那個(gè)Method類的方法invoke,需要兩個(gè)參數(shù)變量:被調(diào)用方法的對(duì)象實(shí)例,以及排成了一個(gè)數(shù)組的參數(shù)列表。注意那個(gè)doNothing()方法是沒有參數(shù)的。就算如此,我們還是需要為它設(shè)置一個(gè)長(zhǎng)度為0的數(shù)組作為參數(shù)變量。
在我們的例子中,方法執(zhí)行后同樣是要返回的。如果這個(gè)方法確實(shí)有返回值的話,那么這回返回值將會(huì)以一個(gè)Object的形式進(jìn)行返回,就如同下面的例子一樣。
Object returnValue = method.invoke(object, new Object[0]);
在上面的這個(gè)例子中,由于這個(gè)方法本身是不返回任何東西的,因此invoke執(zhí)行后返回的值是個(gè)null。注意,這里返回null值可能會(huì)產(chǎn)生一個(gè)小迷惑。有時(shí)我們出于某種目的,方法會(huì)被設(shè)計(jì)成返回null的。
在結(jié)束本節(jié)前,需要強(qiáng)調(diào)的一點(diǎn)是要知道方法會(huì)如同字段一樣是有繼承性的。于是,我們就來創(chuàng)建一個(gè)有效的方法,用它來檢索出那些繼承而來的方法。
public static Method getDeclaredMethod(Object object, String name)
throws NoSuchMethodException {
Method method = null;
Class<?> clazz = object.getClass();
do {
try {
method = clazz.getDeclaredMethod(name);
} catch (Exception e) { }
} while (method == null & (clazz = clazz.getSuperclass()) != null);
if (method == null) {
throw new NoSuchMethodException();
}
return method;
}
最后,我們得出了一個(gè)通用的invoke方法。再次注意一下,方法可能會(huì)被定位為私有,因此需要在調(diào)用它們之前設(shè)置一下它們的可訪問性。
public static Object invokeMethod(Object object, String methodName,
Object[] arguments) throws NoSuchMethodException,
IllegalArgumentException, IllegalAccessException,
InvocationTargetException {
Method method = getDeclaredMethod(object, methodName);
method.setAccessible(true);
return method.invoke(object, arguments);
}
除了可以調(diào)用執(zhí)行那些私有方法外,通過反射來調(diào)用方法能很好地獲知類的功能并在運(yùn)行時(shí)中十分容易地改變它的執(zhí)行流程。
反射應(yīng)用程序
直到現(xiàn)在,我們都僅僅是創(chuàng)建了一些工具方法并試驗(yàn)了一些簡(jiǎn)單的例子。但現(xiàn)實(shí)中的程序要做的遠(yuǎn)不止這些。
Until now, we have only created utility methods and tried out simple examples. Real-life programming requires more than that. Imagine that we need to search through our objects and determine whether a given object matches some criteria or not. The first option is to write an interface and implement it in every object that returns true if this instance matches the criteria, false otherwise. Unfortunately, this approach requires us to implement a method within every class we have. New classes will have to implement this interface and provide a body for its abstract method. Alternatively, we can use reflection to retrieve the object's fields and check whether their values meet the criteria.
Let us first create another method that returns the object's fields. Remember that there's no built-in method that returns all the fields including the inherited ones. Thus we need to retrieve them ourselves by extracting them set by set until we reach the top of the hierarchy. This method can be added to the ReflectionUtil class.
累死了!
未完待續(xù)。。。。