通過上一篇文章我們對Java反射有了一個基本的認(rèn)識,同時簡單了解了Class類以及如何利用反射查看任意對象的數(shù)據(jù)域名城和類型
今天我們將進(jìn)一步查看數(shù)據(jù)域的實際內(nèi)容,學(xué)習(xí)如何在運(yùn)行時使用反射分析對象以及如何使用反射編寫泛型數(shù)組代碼
一、在運(yùn)行時使用反射分析對象###
在編寫程序的時候,如果知道想要查看的域名和類型,查看指定的域是一件很容易的事情。而利用反射機(jī)制可以查看在編譯時還不清楚的對象域。查看對象域的關(guān)鍵方法是 Field類中的get方法。
Employee harry = new Employee("Harry Hacker",35000,10,1,1989);
Class cl = harry.getClass(); //代表一個Employee
Field f = cl.getDeclaredField("name"); //Employee類的name字段
Object v = f.get(harry); //harry對象的name字段的數(shù)值,i.e.,獲得“Harry Hacker”
- 事實上,上面這段代碼存在一個問題。由于name是一個私有域,所以get方法將會拋出一個IllegalAccessException。只有利用get方法才能得到可訪問域的值。除非擁有訪問權(quán)限,否則Java安全機(jī)制值允許查看任意對象有哪些域,而不允許讀取它們的值。
反射機(jī)制的默認(rèn)行為受限于Java的訪問控制。然而,如果一個Java程序沒有受到安全管理器的控制,就可以覆蓋訪問控制。為了達(dá)到這個目的,需要調(diào)用Field、Method和Constructor對象的setAccessible()方法。eg.f.setAccessible(true);設(shè)置后就可以成功調(diào)用f.get(harry)方法了。
PS:setAccessible()方法是AccessibleObject類中的一個方 法,他是Field、Method和Constructor類的公共超類。稍后將利用它編寫一個通用的toString()方法。
-
Field.get()方法還有一個需要解決的問題。name域是一個String,因此把它作為Object返回沒有問題。但是,如果我們想要查看double類型,Java中數(shù)值類型不是對象。想要解決這個問題,可以使用Field類中getDouble()方法,也可以調(diào)用get方法,此時,反射機(jī)制將會自動地將這個域值打包到相應(yīng)的對象包裝器中,這里打包為Double。
當(dāng)然,可以獲得就可以設(shè)置。調(diào)用f.set(obj,value)可以將obj對象的f域設(shè)置成新值。下面將編寫一個可供任意類使用的通用toString()方法。其中使用getDeclaredFileds()獲得所有的數(shù)據(jù)域,然后使用setAccessible()將所有的域設(shè)置為可訪問的。然后對每個域獲取名字和值。再遞歸調(diào)用toString()方法,將每個值轉(zhuǎn)換成字符串。
public class ObjectAnalyzer {
public String toString(Object obj){
Class cl = obj.getClass();
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{
Object val = f.get(obj);
r += toString(val);
}catch (Exception e) {
e.printStackTrace();
}
}
}
r += "]";
cl = cl.getSuperclass();
}
while(cl != null);
return "";
}
測試用例和運(yùn)行結(jié)果:
測試用例:
ArrayList<Integer> squares = new ArrayList<>();
for(int i = 0 ; i <=5 ; i++) squares.add(i*i);
System.out.println(new ObjectAnalyzer().toString(squares));
運(yùn)行結(jié)果:
java.util.ArrayList[e]ementData=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][][]
還可以通過這種方式來實現(xiàn)自己類中的toString()方法,如下:
public String toString(){
return new ObjectAnalyzer().toString(this);
}
PS:之所以上面會有5個null值,是因為我們在初始化ArrayList的時候沒有指定大小,構(gòu)造方法默認(rèn)創(chuàng)建一個大小為10的數(shù)組,所以當(dāng)我們add()5個數(shù)值后,還會有5個null。
上面所示的代碼還是有一點(diǎn)問題的,這只是一個簡單版本,循環(huán)引用將有可能導(dǎo)致無限遞歸從而棧內(nèi)存溢出。之后我們會提供一種記錄已經(jīng)訪問過的對象的方法。
二、使用反射編寫泛型數(shù)組代碼###
java.long.reflect包中的Array類允許動態(tài)地創(chuàng)建數(shù)組。例如,將這個特性應(yīng)用到Array類中的copyOf方法實現(xiàn)中,應(yīng)該記得這個方法可以用于擴(kuò)展已經(jīng)填滿的數(shù)組。
Employee[] a = new Employee[100];
//array is full
a = Arrays.copyOf(a,2*a.length);
如何編寫一個通用的方法,正好能夠?qū)mployee[]數(shù)組轉(zhuǎn)換為Object[]數(shù)組,我們先來進(jìn)行一次簡單的嘗試。
我們先來看一個不太好的方法#####
public static Object[] badCopyOf(Object[] a , int newLength){
Object[] newArray = new Object[newLength];
System.arraycopy(a,0,newArray,0,Math.min(a.length,newLength));
return newArray;
}
這段代碼有一個很嚴(yán)重的問題,我們Employee[]類型可以轉(zhuǎn)換為Object[]類型,但是原本就是Object[]類型的對象就不能轉(zhuǎn)換為Employee[]類型了。為了能夠創(chuàng)建與原數(shù)組類型相同的新數(shù)組,就需要用到java.reflect包中的Array類的一些方法。其中最重要的就是Array類中的靜態(tài)方法newInstance(),能夠構(gòu)造新數(shù)組。Object newArray = Array.newInstance(componentType,newLength)
從上面代碼我們也能看出,需要獲得新數(shù)組的長度和元素類型。具體用代碼來闡述
public static Object goodCopyOf(Object a ,int newLength){
Class cl = a.getClass(); //首先獲得a數(shù)組的類對象
if(!cl.isArray()) return null; //然后確認(rèn)它是一個數(shù)組
Class componentType = cl.getComponentType(); //確定數(shù)組對應(yīng)類型
int length = Array.getLength(a); //獲取a的大小
Object newArray = Array.newInstance(componentType,newLength);
System.arraycopy(a,0,newArray,0,Math.min(length,newLength));
return newArray;
}
PS:這個CopyOf()方法可以用來擴(kuò)展任意類型的數(shù)組,而不僅是對象數(shù)組。
int[] a = {1,2,3,4,5};
a = (int[]) goodCopyOf(a,10);
值得注意的是,上述方法的參數(shù)類型就應(yīng)該顯示為Object而不是Object[]。因為整型數(shù)組類型int[]可以轉(zhuǎn)換為Object,但不能轉(zhuǎn)換成對象數(shù)組。
具體Array還有哪些方法就請自己查看文檔了。Array-API
反射暫時總結(jié)到這兒,以后學(xué)習(xí)了框架源碼后再進(jìn)行擴(kuò)展吧~