Java中的反射庫(reflection library)中提供了一個非常豐富且精心設計的工具集,以便編寫能夠動態操縱Java代碼的程序。
能夠分析類能力的程序稱為反射(reflective)。反射的功能很強大,下面是反射的用途:
在運行中分析類的能力;在運行中查看對象,例如編寫一個toString方法供所有類使用;實現通用的數組操作代碼;利用Method對象,這個對象很像C++中的函數指針;Java是面向對象的語言,在Java中,所有的東西都是類(除了靜態方法和基本類型)。那麼,類是不是一個對象呢?
在Java程序運行期間,Java運行時系統時鐘為所有的對象維護一個被稱為運行時的類型標志。這個信息跟蹤著每個對象的所屬類。虛擬機利用運行時類型信息選擇相應的方法執行。
Class類保存了Java類的這些信息。官網中,對這個類型稱為類的類類型,也就是說一個類的對象。比如,有一個類Student,可以使用下面的代碼創建一個實例:
Stuent stu=new Studnet();即stu是類Student的一個實例。那既然類也是對象,那麼Student是什麼的實例呢?Student是類Class的一個實例。
可以通過如下三種方法獲得一個類的類類型。
(1)getClass方法
如果有一個類的實例,那麼可以通過這個實例的getClass方法獲得這個類的類類型:
Class cl=stu.getClass();(2)靜態方法forName
forName是Class的一個靜態方法,如果沒有一個類的實例,但是知道這個類的名字,可以使用這個方法獲得這個類的類類型:
Class cl=Class.forName("Student");如果類名保存在字符串中,並可在運行中改變,就可以使用這個方法。當然,這個方法只有在參數是類名或接口名的時候才能夠執行,否則forName方法將拋出一個異常。因此,使用這個方法時,應該處理這個異常。
(3).class
第三個獲得類類型 的方法很簡單。如果T是任意的Java類型,T.class將代表匹配的類對象。比如:
Class cl1=Student.class; Class cl2=int.class Class cl3=Double[].class
虛擬機為每個類型管理一個Class對象。因此可以使用==運算符實現兩個類對象的比較:
if(stu.getClass()==Student.class)...這將判斷為真。
Class中最常用的一個方法就是getName方法,這個方法將返回類的名字。
還可以通過類類型創建一個類的實例。比如:
Class cl=Student.class; Student stu=(Student)cl.newInstance();
使用forName和newInstance方法可以根據存儲在字符串中的類名創建一個實例:
String s="java.util.Date"; Object date=Class.forName(s).newInstance();
在第一節中,Class類的forName方法可能會拋出一個異常,可以使用下面的代碼捕獲並處理異常:
try{ String name=...;//get class name Class cl=Class.forName(name);//might throw exception do something with cl }catch(Exception e){ e.printStackTrace(); }
下面簡要的介紹一下反射機制最重要的內容:檢查類的結構。
在java.lang.reflect包中有三個類Field、Method和Constructor分別描述類的域、方法和構造器。這三個類都有一個叫getName的方法,用來返回項目的名稱。Field類有一個getType方法,用來返回描述域所屬的Class對象。Method和Constructor類有能夠報告參數類型的方法,Method還有一個可以報告返回類型的方法。這三個類有一個getModifiers的方法,它將返回一個整數值,用不同的位開關描述public和static這樣的修飾符的使用狀況,還可以利用Modifier.toString方法將修飾符打印出來。
Class類的getFields、getMethods和getCostructors方法可以獲得類提供的public域、方法和構造器數組,其中包括超類的共有成員。Class類的getDeclatedFields、getDeclatedMethods和getDeclaredConstructors方法可以獲得類中聲明的全部域、方法和構造器,其中包括私有和受保護的成員,但不包括超類的成員。
Constructor類:
String getName():返回構造器的名字;int getModifiers():返回修飾符;int getParameterCount():返回構造器中參數的個數;Class[] getParameterTypes():返回參數的類型;Field類:
String getName():返回域的名字;int getmodifiers():返回修飾符;ClassgetType():返回域的類類型;Method類:
String getName():返回方法的名字;int getModifiers():返回修飾符;int getParameterCount():返回參數的個數;Class[] getParameterTypes():返回參數的類類型;ClassgetReturnType():返回返回類類型;下面的代碼編寫了一個可以打印一個類的所有構造器、方法和域的相關信息的類:
package reflection; import java.lang.reflect.*; public class PrintClassInfo { private PrintClassInfo(){} public static void printClassInfo(String name){ try{ Class cl=Class.forName(name); Class supercl=cl.getSuperclass(); String modifiers=Modifier.toString(cl.getModifiers()); if(modifiers.length()>0)System.out.print(modifiers+" "); System.out.print("class "+name); if(supercl!=null&&supercl!=Object.class)System.out.print(" extends "+supercl.getName()); System.out.print("\n{\nConstructors:\n"); printConstructors(cl); System.out.println("Methods:"); printMethods(cl); System.out.println("Fields:"); printFields(cl); System.out.println("}"); }catch(ClassNotFoundException e){ e.printStackTrace(); } System.exit(0); } public static void printConstructors(Class cl){ Constructor[] cons=cl.getConstructors(); for(Constructor c:cons){ String name=c.getName(); System.out.print(" "); String modifiers=Modifier.toString(c.getModifiers()); if(modifiers.length()>0)System.out.print(modifiers+" "); System.out.print(name+"("); Class[] paramTypes=c.getParameterTypes(); for(int i=0;i0)System.out.print(", "); System.out.print(paramTypes[i].getName()); } System.out.println(");"); } } public static void printMethods(Class cl){ Method[] methods=cl.getDeclaredMethods(); for(Method m:methods){ Class retType=m.getReturnType(); String name=m.getName(); System.out.print(" "); String modifiers=Modifier.toString(m.getModifiers()); if(modifiers.length()>0)System.out.print(modifiers+" "); System.out.print(retType.getName()+" "+name+"("); Class[] paramTypes=m.getParameterTypes(); for(int i=0;i 0)System.out.print(", "); System.out.print(paramTypes[i].getName()); } System.out.println(");"); } } public static void printFields(Class cl){ Field[] fields=cl.getDeclaredFields(); for(Field f:fields){ Class type=f.getType(); String name=f.getName(); System.out.print(" "); String modifiers=Modifier.toString(f.getModifiers()); if(modifiers.length()>0)System.out.print(modifiers+" "); System.out.println(type.getName()+" "+name+";"); } } }
String name; System.out.println("Enter the class name:"); Scanner scanner=new Scanner(System.in); name=scanner.next(); PrintClassInfo.printClassInfo(name);運行輸入java.lang.String,結果如下:
public final class java.lang.String
{
Constructors:
public java.lang.String([B, int, int);
public java.lang.String([B, java.nio.charset.Charset);
public java.lang.String([B, java.lang.String);
public java.lang.String([B, int, int, java.nio.charset.Charset);
public java.lang.String([B, int, int, java.lang.String);
public java.lang.String(java.lang.StringBuilder);
public java.lang.String(java.lang.StringBuffer);
public java.lang.String([B);
public java.lang.String([I, int, int);
public java.lang.String();
public java.lang.String([C);
public java.lang.String(java.lang.String);
public java.lang.String([C, int, int);
public java.lang.String([B, int);
public java.lang.String([B, int, int, int);
Methods:
public boolean equals(java.lang.Object);
public java.lang.String toString();
public int hashCode();
public int compareTo(java.lang.String);
public volatile int compareTo(java.lang.Object);
public int indexOf(java.lang.String, int);
public int indexOf(java.lang.String);
public int indexOf(int, int);
public int indexOf(int);
static int indexOf([C, int, int, [C, int, int, int);
static int indexOf([C, int, int, java.lang.String, int);
public static java.lang.String valueOf(int);
public static java.lang.String valueOf(long);
public static java.lang.String valueOf(float);
public static java.lang.String valueOf(boolean);
public static java.lang.String valueOf([C);
public static java.lang.String valueOf([C, int, int);
public static java.lang.String valueOf(java.lang.Object);
public static java.lang.String valueOf(char);
public static java.lang.String valueOf(double);
public char charAt(int);
private static void checkBounds([B, int, int);
public int codePointAt(int);
public int codePointBefore(int);
public int codePointCount(int, int);
public int compareToIgnoreCase(java.lang.String);
public java.lang.String concat(java.lang.String);
public boolean contains(java.lang.CharSequence);
public boolean contentEquals(java.lang.CharSequence);
public boolean contentEquals(java.lang.StringBuffer);
public static java.lang.String copyValueOf([C);
public static java.lang.String copyValueOf([C, int, int);
public boolean endsWith(java.lang.String);
public boolean equalsIgnoreCase(java.lang.String);
public static transient java.lang.String format(java.util.Locale, java.lang.String, [Ljava.lang.Object;);
public static transient java.lang.String format(java.lang.String, [Ljava.lang.Object;);
public void getBytes(int, int, [B, int);
public [B getBytes(java.nio.charset.Charset);
public [B getBytes(java.lang.String);
public [B getBytes();
public void getChars(int, int, [C, int);
void getChars([C, int);
private int indexOfSupplementary(int, int);
public native java.lang.String intern();
public boolean isEmpty();
public static transient java.lang.String join(java.lang.CharSequence, [Ljava.lang.CharSequence;);
public static java.lang.String join(java.lang.CharSequence, java.lang.Iterable);
public int lastIndexOf(int);
public int lastIndexOf(java.lang.String);
static int lastIndexOf([C, int, int, java.lang.String, int);
public int lastIndexOf(java.lang.String, int);
public int lastIndexOf(int, int);
static int lastIndexOf([C, int, int, [C, int, int, int);
private int lastIndexOfSupplementary(int, int);
public int length();
public boolean matches(java.lang.String);
private boolean nonSyncContentEquals(java.lang.AbstractStringBuilder);
public int offsetByCodePoints(int, int);
public boolean regionMatches(int, java.lang.String, int, int);
public boolean regionMatches(boolean, int, java.lang.String, int, int);
public java.lang.String replace(char, char);
public java.lang.String replace(java.lang.CharSequence, java.lang.CharSequence);
public java.lang.String replaceAll(java.lang.String, java.lang.String);
public java.lang.String replaceFirst(java.lang.String, java.lang.String);
public [Ljava.lang.String; split(java.lang.String);
public [Ljava.lang.String; split(java.lang.String, int);
public boolean startsWith(java.lang.String, int);
public boolean startsWith(java.lang.String);
public java.lang.CharSequence subSequence(int, int);
public java.lang.String substring(int);
public java.lang.String substring(int, int);
public [C toCharArray();
public java.lang.String toLowerCase(java.util.Locale);
public java.lang.String toLowerCase();
public java.lang.String toUpperCase();
public java.lang.String toUpperCase(java.util.Locale);
public java.lang.String trim();
Fields:
private final [C value;
private int hash;
private static final long serialVersionUID;
private static final [Ljava.io.ObjectStreamField; serialPersistentFields;
public static final java.util.Comparator CASE_INSENSITIVE_ORDER;
}
值得注意的是,這個程序可以分析Java解釋器能夠加載的任何類,而不僅僅是編譯程序時可以使用的類。
類的加載有兩種方式,一個是在編譯時加載的靜態加載類,另一個是在運行時加載的動態加載類。當我們使用new來創建一個對象時,使用的是靜態加載類,這個時候必須保證類已經實現。而Class的forName方法不但可以獲得一個類的類類型,還是一個動態加載類的方法,可以越過編譯,在運行時加載一個類。
動態加載有什麼好處麼?考慮一個場景,比如自己要實現一個辦公軟件Office,裡面有各種組件Word和Excel等。有一個Office工具可以啟動各種組件,由於現階段只有兩個組件Word和Excel,那麼Office可以這樣編寫:
class Office { public static void main(String[] args) { if("Word".equals(args[0])) { Word w=new Word(); w.start(); } if("Excel".equals(args[0])) { Excel e=new Excel(); e.start(); } } }
這裡使用的就是靜態加載,因為使用new創建的一個對象。不過,如果開發Word的組工作較快,已經完成了開發,代碼假如如下:
class Word { public void start() { System.out.println("Word running..."); } }
說找不到Excel類。這樣,整個的Office都不能使用了。
這時,就可以使用動態加載了。使用Class的forName方法動態加載一個類:
Word w=(Word)Class.forName(args[0]);
interface OfficeAble { void start(); }
class Word implements OfficeAble { public void start() { System.out.println("Word running..."); } }這樣,在OfficeBetter類中就可以使用接口了:
class OfficeBetter { public static void main(String[] args) { try { Class cl=Class.forName(args[0]); OfficeAble oa=(OfficeAble)cl.newInstance(); oa.start(); } catch(Exception e) { e.printStackTrace(); } } }注意,forName會拋出異常,要進行捕獲並處理。
這時,編譯Word和OfficeBetter:
編譯沒有錯誤了,運行也正確。
當啟動Excel時就會報錯:
說沒有找到Excel類。
當Excel實現完後,只需要編譯Excel,不需要編譯OfficeBetter就可以運行:
結果正確。這樣,使用動態加載類,就可以隨時添加功能而不需要重新編譯。
現在已經知道了如何查看任意對象的數據域名稱和類型:
(1)獲得對應的類類型;
(2)通過類類型調用getDeclaredFields方法;
利用反射機制可以查看在編譯時還不清楚的對象域。
查看對象域的關鍵方法是Field類中的get方法。如果f是一個Field類型的對象,obj是某個包含f域的類的對象,f.get(obj)將返回一個對象,其值為obj與的當前值。
既然能夠得到域的值,那麼也就能設置域的值。可以使用Field類中的set方法設置域的值,用法為f.set(obj,value),obj是包含f域的對象,value是要設置的值。
還要注意,如果f是一個私有域,那麼直接使用get方法會拋出一個IllegalAccessException異常。除非擁有訪問權限,否則Java安全機制只允許查看任意對象有哪些域而不允許得去域的值。
反射機制的默認行為受限於Java的訪問控制。然而,如果一個Java程序沒有受到安全管理器的控制,就可以覆蓋訪問控制。可以調用Field、Method或Constructor類中的setAccessible方法達到這個目的:
f.setAccessible(true);
setAccessible方法是AccessibleObject類中的一個方法,它是Field、Method和Constructor類的超類。
下面的代碼演示了使用get和set方法獲得和設置域的值:
Student stu=new Student("Bai",20,99); Class cl=stu.getClass(); Field f=cl.getDeclaredField("name"); f.setAccessible(true); String stuname=(String)f.get(stu); System.out.println("The name is:"+stuname); f.set(stu, "Liu"); System.out.println("The name is:"+(String)f.get(stu));其中Student類包含String類型的name、int類型的age和double類型的score。
運行結果如下:
The name is:Bai
The name is:Liu
即成功獲得和設置域的值。
不過get還有一個問題,就是name是一個String,因此把它當做Object返回沒有問題。但是如果要返回double的score,double不是對象,這時可以使用Field類中的getDouble方法。此時,反射機制會自動將這個域值打包到相應的對象包裝器中。
Field fs=cl.getDeclaredField("score"); fs.setAccessible(true); System.out.println("The score is:"+fs.getDouble(stu));
The score is:99.0
在C和C++中,可以從函數指針執行任意函數。雖然Java沒有提供函數指針,但反射機制允許調用任意方法。
和Field中的get方法類似,Method類中有個invoke方法,它允許調用包裝在當前Method對象中的方法。invoke方法的簽名是:
Object invoke(Object obj,Object... args);
在一個類中,必須知道哪些信息才能唯一確定一個方法呢?
由於Java中可以有同名的方法,因此可以使用參數來進行區分。不過要注意,返回類型不能作為區分兩個函數的依據。因此,在使用Method類中的getMethod和getDeclaredMethod方法時,要給出方法名和方法參數:
Method getMethod(String name,Class... parameterTypes)
import java.lang.reflect.*; public class InvokeTest { public static void main(String[] args) { A a=new A(); Class cl=a.getClass(); try { Method m1=cl.getMethod("add", new Class[]{String.class,String.class}); m1.invoke(a, new Object[]{"hello","world"}); Method m2=cl.getMethod("add", int.class,int.class); m2.invoke(a, 1,2); Method m3=cl.getMethod("add"); m3.invoke(a); Method m4=cl.getMethod("add", String.class,int.class); m4.invoke(null, "Liu",100); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } class A{ public void add(){ System.out.println("do nothing..."); } public void add(int a,int b){ System.out.println(a+b); } public void add(String a,String b){ System.out.println(a.toUpperCase()+b.toUpperCase()); } public static void add(String name,int age){ System.out.println(name+" is "+age+" years old."); } }
第一種,構造一個Class數組:
Method m1=cl.getMethod("add", new Class[]{String.class,String.class});
Method m2=cl.getMethod("add", int.class,int.class);
這裡要注意的是,對於靜態方法,沒有包含它的對象,這時第一個參數置為null即可。
運行結果如下:
HELLOWORLD
3
do nothing...
Liu is 100 years old.
我們知道,集合中的泛型使得一個集合中只能保存一種類型數據。比如ArrayList
泛型的使用在編寫代碼中起到了一種類型檢查的作用。我們知道,泛型只是編譯器的功能,Java虛擬機並沒有泛型,也就是說,是不是如果繞過編譯,就能在泛型中添加別的類型的數據呢?比如在ArrayList
下面的代碼演示了這種情況:
import java.lang.reflect.*; import java.util.ArrayList; public class MethodDemo { public static void main(String[] args) { ArrayList list=new ArrayList(); ArrayListlist1=new ArrayList<>(); System.out.println(list.getClass()==list1.getClass()); } }
true
說明在編譯階段是去泛型化的。
接下來修改代碼如下:
import java.lang.reflect.*; import java.util.ArrayList; public class MethodDemo { public static void main(String[] args) { ArrayList list=new ArrayList(); ArrayListlist1=new ArrayList<>(); list.add(10); list.add("hello"); list1.add("world"); //list1.add(20);ERROR:can't add int to ArrayList try { Method m=list1.getClass().getDeclaredMethod("add", Object.class); m.invoke(list1, 20); System.out.println(list1.size()); System.out.println(list1); for(String s:list1){ System.out.print(s+" "); } } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } }
長度為2,內容也正確,說明我們跳過了編譯階段的類型檢查,在運行時成功添加了int類型數據。不過,使用for循環遍歷是報錯了,因為20不是String類型數據。
通過這個例子,可以知道,泛型會在編譯時去泛型化。同時可以通過反射在運行階段添加數據。
java.lang.reflect包中的Array類可以動態創建數組。比如,可以將這個特性應用到Array類的copyOf方法實現中。
如果要給一個Student[]數組復制,可以先將Student[]轉換為Object[]數組,比如這樣:
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; }不過,這樣的話,返回的類型是Object[],而Object[]不能轉化為Student[]類型。
為了編寫通用的方法,需要創建一個與原數組類型相同的新數組。為此,需要java.lang.reflect包中的Array類中的一些方法。
Array類中有一個newInstance方法,能夠創建新數組。這個方法要兩個參數,一個是數組的元素類型,另一個是數組的長度:
Object newArray=Array.newInstance(componentType,newLength);這樣,就需要獲得數組元素的類型和數組的長度。使用Array類中的getComponentType方法可以獲得元素的類型,使用Array類中的getLength方法可以獲得數組的長度。
下面是完整的代碼:
import java.lang.reflect.*; import java.util.Arrays; public class CopyOfTest { public static void main(String[] args) { int[] a={1,2,3}; a=(int[])goodCopyOf(a,10); System.out.println(Arrays.toString(a)); String[] b={"Tom","Dick","Harry"}; b=(String[])goodCopyOf(b,10); System.out.println(Arrays.toString(b)); b=(String[])badCopyOf(b,10); } 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; } public static Object goodCopyOf(Object a,int newLength){ Class cl=a.getClass(); if(!cl.isArray())return null; Class componentType=cl.getComponentType(); int length=Array.getLength(a); Object newArray=Array.newInstance(componentType, newLength); System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength)); return newArray; } }
結果如下:
注意,應該將goodCopyOf的參數聲明為Object類型,而不要聲明成Object[]。因為正數數組類型int[]可以轉換成Object,但不能轉換成對象數組。