在運行時使用反射分析對象
前面,我們已經知道如何查看任意對象數據域名稱和類型:
- 獲得對應的Class對象。
- 獲得Class對象調用getDeclaredFields。
現在我們可以進一步查看數據域的實際內容。當然,在編寫程序時,如果知道想要查看的域名和類型,查看指定的域是一件很容易的事情。而利用反射機制可以查看在編譯時還不清楚的對象域。
查看對象域的關鍵方法是Field類中的get方法。如果f是一個Field類型的對象,obj是某個包含f域的類的對象,f.get(obj)將返回一個對象,其值爲obj域的當前值。如:
Employee harry = new Employee("Harry Hacker", 35000, 10, 1, 1989);
Class cl = harry.getClass();
Field f = cl.getDeclaredField("name");
Object v = f.get(harry);
實際上,這段代碼存在一個問題。由於name是一個私有域,所以get方法將會拋出一個IllegalAccessException。只有利用get方法才能得到可訪問域的值。除非擁有訪問權限,否則Java安全機制只允許查看任意對象有哪些域,而不允許讀取它們的值。
反射機制的默認行爲受限於Java的訪問控制。然而,如果一個Java程序沒有收到安全管理器的控制,就可以覆蓋訪問控制。爲了打到這個目的,需要調用Field、Method或Constructor對象的setAccessible方法。如:
f.setAccessible(true);
setAccessible方法是AccessibleObject類中的一個方法,它是Field、Method和Constructor類的公共超類。這個特性是爲調試、持久存儲和相似機制提供的。
get方法還有一個需要解決的問題。name域是一個String,因此把它作爲Object返回沒有什麼問題。但是,假定我們想要查看salary域。它屬於double類型,而Java中數值類型不是對象,想要解決這個問題,可以使用Field類中的getDouble方法,也可以調用get方法,此時,反射機制會自動地將這個域值打包到相應的對象包裝器中,這裏將打包成Double。
當然,可以獲得就可以設置。調用f.set(obj, value)可以將obj對象的f域設置成新值。
實例
我們編寫一個可供任意類使用的通用toString方法。
ObjectAnalyzer.java
package cn.freedompc.objectanalyzer;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
public class ObjectAnalyzer {
private ArrayList<Object> visited = new ArrayList<>();
public String toString(Object obj) {
if (obj == null) return "null";
if (visited.contains(obj)) return "...";
visited.add(obj);
Class cl = obj.getClass();
if (cl == String.class) return (String)obj;
if (cl.isArray()) {
String r = cl.getComponentType() + "[]{";
for (int i = 0; i < Array.getLength(obj); i++) {
if (i > 0) r += ",";
Object val = Array.get(obj, i);
if (cl.getComponentType().isPrimitive()) r+= val;
else r += toString(val);
}
return r + "}";
}
String r = cl.getName();
do {
r += "[";
Field[] fields = cl.getDeclaredFields();
AccessibleObject.setAccessible(fields, true);
for (Field f : fields) {
if (!Modifier.isStatic(f.getModifiers())) {
if (!r.endsWith("[")) r += ",";
r += f.getName() + "=";
try {
Class t = f.getType();
Object val = f.get(obj);
if (t.isPrimitive()) r += val;
else r += toString(val);
} catch(Exception e) {
e.printStackTrace();
}
}
}
r += "]";
cl = cl.getSuperclass();
} while (cl != null);
return r;
}
}
ObjectAnalyzerTest.java
package cn.freedompc.objectanalyzer;
import java.util.ArrayList;
public class ObjectAnalyzerTest {
public static void main(String[] args) {
ArrayList<Integer> squares = new ArrayList<>();
for (int i = 1; i <= 5; i++)
squares.add(i * i);
System.out.println(new ObjectAnalyzer().toString(squares));
}
}
結果
java.util.ArrayList[elementData=class java.lang.Object[]{java.lang.Integer[value=1][][],java.lang.Integer[value=4][][],java.lang.Integer[value=9][][],java.lang.Integer[value=16][][],java.lang.Integer[value=25][][],null,null,null,null,null},size=5][modCount=5][][]
分析
這個程序使用了getDeclaredFields獲得所有的數據域,然後使用setAccessible將所有的域設置爲可訪問的。對於每個域,獲得了名字和值,遞歸(比較複雜,以後會說到)調用toString方法,將每個值轉換成字符串。
這個程序是一種公認的提供toString方法的手段,在編寫程序時你會發現,它是非常有用的。
捐贈
若你感覺讀到這篇文章對你有啓發,能引起你的思考。請不要吝嗇你的錢包,你的任何打賞或者捐贈都是對我莫大的鼓勵。