【java基礎(三十三)】反射(三)

在運行時使用反射分析對象

前面,我們已經知道如何查看任意對象數據域名稱和類型:

  • 獲得對應的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方法的手段,在編寫程序時你會發現,它是非常有用的。

捐贈

若你感覺讀到這篇文章對你有啓發,能引起你的思考。請不要吝嗇你的錢包,你的任何打賞或者捐贈都是對我莫大的鼓勵。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章