Java反射總結
能夠分析類能力的程式稱為反射。對於給定的Java類名,可以通過反射獲取類的資訊、將類的各成分映射出相應的Java類。
Class類
在程式執行期間,Java執行時系統始終對所有的物件維護一個被稱為執行時的型別標識。這個資訊跟蹤著每個物件所屬的類。虛擬機器利用執行時型別資訊選擇相應的方法執行。可以通過專門的Java類訪問這些資訊。儲存這些資訊的類被稱為Class。
建立Class類物件的三種方法:
1. 通過getClass方法
Object中的getClass
方法將返回一個Class型別的例項。
class Person { //... } Person p = new Person(); Class c = p.getClass();
2. forName方法
可以通過靜態方法forName
獲得類名對應的Class物件。
String className = "java.util.Date"; Class c = Class.forName(className);
如果需要在執行過程中需要改變類名,就可以使用這種方法。當然,字串className必須是一種類名或介面(包括包名),否則會出現異常。
3. T.class
Class c1 = Date.class; Class c2 = int.class; Class c3 = Double[].class;
T表示任意的Java型別。通過T.class獲取匹配的類物件。
注意:一個Class物件實際表示一種型別,而這種型別未必是一種類。
虛擬機器為每個型別管理一個Class物件。因此可以用==運算子來實現兩個類物件的比較
if (p.getClass() == Person.class) {...}
newInstance方法
通過newInstance
方法可以建立一個類的例項。
Person person = p.getClass().newInstance();
建立了一個與p具有相同類型別的例項。newInstance呼叫預設的構造器初始化建立的物件。如果這個類沒有預設的構造器,就會丟擲異常。
利用反射解析類結構
獲取包名+類名(包括父類名)
package com.xiaoxiaoyihan.reflection; class Person { //... } class Student extends Person{ } public class ReflectionDemo1 { public static void main(String[] args) { Student s = new Student(); Class cl = s.getClass(); Class superCl = cl.getSuperclass(); System.out.println("獲取類名:" + cl.getName()); System.out.println("獲取父類名:" + superCl.getName()); } }
【執行結果】:
獲取類名:com.xiaoxiaoyihan.reflection.Student
獲取父類名:com.xiaoxiaoyihan.reflection.Person
說明:如果類在一個包中,包的名字也作為類名的一部分。
一點擴充套件:
首先看一個例子:
class Person { private String name = "蕭蕭弈寒"; // 省略setter和getter } class Animal { private String name = "paqi"; // 省略setter和getter } Person p; Animal a; Class cPerson = p.getClass(); Class cAnimal = a.getClass(); // cPerson.getName()獲取的是類名、p.getName()是Person例項的name屬性值 System.out.println(cPerson.getName() + "<--->" + p.getName()); System.out.println(cAnimal.getName() + "<--->" + p.getName());
【執行結果】:
com.xxyh.reflec.Person<--->蕭蕭弈寒
com.xxyh.reflec.Animal<--->paqi
由此說明,一個Person物件p表示一個特定人的屬性,一個Animal物件a表示一個特定動物的屬性,一個Class物件表示一個特定類(Person或Animal)的屬性。從這點看,Class的例項是一種物件。
解析建構函式(Constructor)
Class類中的getConstructors
方法將返回公共構造器陣列。Class類中的getDeclaredConstructors將返回類中宣告的構造器陣列。
package com.xiaoxiaoyihan.reflection; import java.lang.reflect.Constructor; class Person { private String name; private int age; private Person() {} protected Person(String name) { this.name = name; } public Person(String name, int age) { this.name = name; this.age = age; } } public class ReflectionDemo1 { public static void main(String[] args) throws ClassNotFoundException { Class cl = Class.forName("com.xiaoxiaoyihan.reflection.Person"); // 直接丟擲異常以簡化程式碼 Constructor[] constructors = cl.getDeclaredConstructors(); //Constructor[] constructors = cl.getConstructors(); for (Constructor c :constructors) { System.out.println(c); } } }
【執行結果】:
private com.xiaoxiaoyihan.reflection.Person()
protected com.xiaoxiaoyihan.reflection.Person(java.lang.String)
public com.xiaoxiaoyihan.reflection.Person(java.lang.String,int)
// public com.xiaoxiaoyihan.reflection.Person(java.lang.String,int)// 本部分為執行getConstructors方法輸出結果
對結果加以分析,會發現通過System.out.println(c);
直接列印的建構函式是由幾部分組成的,其中包括了修飾符(public/protected/private)、類名以及建構函式的引數,那麼,這些部分是如何獲取的呢?
在Modifier
類中包含靜態方法getModifiers
方法,它返回一個整數i,用不同的數值表示public、static、final這樣的修飾符。Modifier中的靜態方法toString(i)
返回對應的修飾符。Constructor
類中包含由靜態方法getParameterTypes
,它返回一個描述引數型別的Class物件陣列。
package com.xiaoxiaoyihan.reflection; import java.lang.reflect.Constructor; import java.lang.reflect.Modifier; class Person { private String name; private int age; private Person() {} protected Person(String name) { this.name = name; } public Person(String name, int age) { this.name = name; this.age = age; } } public class ReflectionDemo1 { public static void main(String[] args) throws ClassNotFoundException { Class cl = Person.class; Constructor[] constructors = cl.getDeclaredConstructors(); for (Constructor c :constructors) { // 獲取包名和類名 String name = c.getName(); // 獲取修飾符 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; i < paramTypes.length; i++) { if (i > 0) { // 如果不止一個引數,使用","將引數型別分割 System.out.print(","); } System.out.print(paramTypes[i].getName()); } System.out.println(");"); } } }
【執行結果】:
private com.xiaoxiaoyihan.reflection.Person();
protected com.xiaoxiaoyihan.reflection.Person(java.lang.String);
public com.xiaoxiaoyihan.reflection.Person(java.lang.String,int);
解析方法(Method)
Class類中的getMethods
將返回方法的陣列,其中包括本類的所有公共方法、從介面實現的方法、父類的公共(public)方法、父類的父類的公共方法……一直延伸到Object的公共方法。getDeclaredMethods
方法將返回本類宣告的方法、從介面實現的方法。
package com.xiaoxiaoyihan.reflection; import java.lang.reflect.Constructor; import java.lang.reflect.Method; class Person/* extends Object */ { private void privateMethodPerson() { //... } protected void protectedMethodPerson() { // ... } public void publicMethodPerson() { //... } } interface Smoke { void smoking(); } class Student extends Person implements Smoke{ @Override public void smoking() { // ... } private void privateMethodStudent() { //... } protected void protectedMethodStudent() { // ... } public void publicMethodStudent() { //... } } public class ReflectionDemo1 { public static void main(String[] args) { Student s = new Student(); Class cl = s.getClass(); Method[] methods = cl.getDeclaredMethods(); // Method[] methods = cl.getMethods(); for (Method m : methods) { System.out.println(m); } } }
【執行結果】:
public void com.xiaoxiaoyihan.reflection.Student.smoking()
protected void com.xiaoxiaoyihan.reflection.Student.protectedMethodStudent()
private void com.xiaoxiaoyihan.reflection.Student.privateMethodStudent()
public void com.xiaoxiaoyihan.reflection.Student.publicMethodStudent()
上面的例子故意給出一種繼承結構,是為了檢視getMethods
的結果,在此就不給出結果了。注意Person預設繼承了Object類。順便說一句,筆者(非科班自學菜鳥)曾在面試的時候被問到Object中有哪些方法,估計他是想問有哪些常用方法吧,我沒答全~2333,不知道讀者您能一眼看出結果嗎??。
類似地,我們看到上面的System.out.println(m);
打印出方法由修飾符、返回值、類名、方法名以及引數組成。那麼這些部分是如果獲取的呢? 與Construction類相似,Method類中也提供了靜態方法getParamTypes
,該方法返回描述引數型別的Class物件陣列。此外,Method還提供了getReturnType
方法,用於獲取返回型別的Class物件。
package com.xiaoxiaoyihan.reflection; import java.lang.reflect.Method; import java.lang.reflect.Modifier; class Person { private String name = "蕭蕭弈寒"; public String getName() { return name; } public void setName(String name) { this.name = name; } public static void speak() { // ... } public final void eat() { // ... } } public class ReflectionDemo1 { public static void main(String[] args) { Class cl = Person.class; Method[] methods = cl.getDeclaredMethods(); for (Method m : methods) { // 返回型別的Class物件 Class retType = m.getReturnType(); // String retTypeName = retType.getName(); // 獲取方法名 String name = m.getName(); String modifiers = Modifier.toString(m.getModifiers()); if (modifiers.length() > 0) // 如果有修飾符 System.out.print(modifiers + " "); // 返回值名 System.out.print(retType.getName() + " "); System.out.print(name + "("); Class[] paramTypes = m.getParameterTypes(); for (int i = 0; i < paramTypes.length; i++) { if (i > 0) { // 如果不止一個引數,用","分割 System.out.print(paramTypes[i].getName()); } } System.out.println(");"); } } }
【執行結果】:
public java.lang.String getName();
public void setName();
public static void speak();
public final void eat();
解析域(Field)
Class類中的getDeclaredFields
方法返回類中宣告的域陣列,getFields方法返回類中的公有域、介面中的域所組成的Field物件陣列。
package com.xiaoxiaoyihan.reflection; import java.lang.reflect.Field; import java.util.Date; class Person { private String name; protected int age; public Date birthday; } class Student extends Person implements Smoke{ private float score; } interface Smoke { String brand = "大中華"; } public class ReflectionDemo1 { public static void main(String[] args) { Class cl = Student.class; Field[] fields = cl.getFields(); for (Field f : fields) { System.out.println(f); } } }
【執行結果】:
public static final java.lang.String com.xiaoxiaoyihan.reflection.Smoke.brand
public java.util.Date com.xiaoxiaoyihan.reflection.Person.birthday
結果顯示了欄位由修飾符、型別、類名、欄位名構成。這裡需要引入Field類中的getType方法,它返回欄位宣告型別的Class物件。下面同樣作出解析:
package com.xiaoxiaoyihan.reflection; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Date; class Person { private String name; protected int age; public Date birthday; } public class ReflectionDemo1 { public static void main(String[] args) { Class cl = Person.class; Field[] fields = cl.getDeclaredFields(); for (Field f : fields) { // 屬性型別的Class物件 Class type = f.getType(); // 屬性型別名 String name = type.getName(); System.out.print(" "); // 修飾符 String modifiers = Modifier.toString(f.getModifiers()); if (modifiers.length() > 0) { // 如果有修飾符 System.out.print(modifiers + " "); } System.out.println(type.getName() + " " + name + ";"); } } }
【執行結果】:
private java.lang.String java.lang.String;
protected int int;
public java.util.Date java.util.Date;
反射的應用
操作物件屬性(域)的值
前面已經知道如何檢視任意物件的資料域名稱和型別。在編寫程式時,如果想要檢視域名和型別是很簡單的事情,而反射機制可以檢視在編譯時還不清楚的物件域。
利用get方法獲取域(屬性)值
檢視物件域的關鍵方法是Field類的get方法。如果f是一個Field型別的物件,obj是某個包含f域的類的物件,f.get(obj)將返回一個物件,型別為Object,其值為obj域的當前值。示例:
package com.xiaoxiaoyihan.reflection; import java.lang.reflect.Field; class Person { private String name; public Person(String name) { this.name = name; } } public class ReflectionDemo2 { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { Person p = new Person("蕭蕭弈寒"); Class clz = p.getClass(); Field f = clz.getDeclaredField("name"); //f.setAccessible(true); Object name = f.get(p); System.out.println(name); } }
【執行結果】:
Exception in thread "main" java.lang.IllegalAccessException: Class com.xiaoxiaoyihan.reflection.ReflectionDemo2 can not access a member of class com.xiaoxiaoyihan.reflection.Person with modifiers "private" at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:109) ……
說明:因為反射機制的預設行為受限於Java的訪問控制。如果一個Java程式沒有受限於安全管理器的控制,就可以覆蓋訪問控制。Field,Method或Constructor物件提供了setAccessible方法用於覆蓋訪問控制。
get方法還需要解決另一個問題,get方法的返回值是Object型別的,上面的name是String型別的,將String型別值,賦給Object物件沒有問題,但是如果出現double型別的域呢?Java中的數值型別不是物件。可以通過Field類的getDouble方法,也可以呼叫get方法然後進行強制型別轉換。反射機制會自動地將這個域值打包到相應的物件包裝器中,對於double型別,將打包成Double。
package com.xiaoxiaoyihan.reflection; import java.lang.reflect.Field; class Person { private double salary; public Person(double salary) { this.salary = salary; } } public class ReflectionDemo2 { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { Person p = new Person(100); Class clz = p.getClass(); Field f = clz.getDeclaredField("salary"); f.setAccessible(true); // double salary = (double) f.get(p); double salary = f.getDouble(p); System.out.println("薪水:" + salary); } }
【執行結果】: 薪水:100.0
利用set方法設定域(屬性)值
當然也可以使用set方法,對obj物件的f域設定新值。set方法的簽名是:
void set(obj, value) // obj:操作的物件;value:新值
示例:
package com.xiaoxiaoyihan.reflection; import java.lang.reflect.Field; class Person { private String name; private double salary; public Person(String name, double salary) { this.name = name; this.salary = salary; } public double getSalary() { return salary; } public String getName() { return name; } } public class ReflectionDemo2 { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { Person p = new Person("張三", 100); System.out.println(p.getName() + "-----" + p.getSalary()); Class clz = Person.class; Field f = clz.getDeclaredField("name"); f.setAccessible(true); f.set(p, "蕭蕭弈寒"); f = clz.getDeclaredField("salary"); f.setAccessible(true); f.set(p, 200); System.out.println(p.getName() + "-----" + p.getSalary()); } }
呼叫任意方法
invoke方法
與Field類的get方法檢視物件的域相似,Method類提供了一個invoke方法,它允許呼叫包裝在當前Method物件中的方法。invoke方法的簽名是:
Object invoke(Object obj, Object...args)
第一個引數是隱式引數,其餘的物件提供了顯示引數。
例如,m1代表Person的getName方法,那麼通過invoke方法獲取name的形式為:
String n = m1.invoke(p);
如果返回的是基本型別,invoke方法會返回其包裝器型別。例如,m2表示getSalary方法,那麼返回的物件實際是一個Double,必須響應的完整型別轉換。
double s = m2.invoke(p);
根據前面的介紹,可以通過Class類的靜態方法getDeclaredMethods獲取Method物件陣列,然後對返回的陣列進行遍歷,找到指定的方法。與getField方法類似,getField方法通過表示域名的字串,返回一個Field物件。然而,由於方法存在過載的情況,有可能獲得若干個相同名字的方法。因此,還必須提供方法的引數型別。getMethod方法的簽名:
Method getMethod(String name, Class...paramTypes)
示例:
package com.xiaoxiaoyihan.reflection; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; class Person { private String name; private double salary; public String getName() { return name; } public double getSalary() { return salary; } public void raiseSalary(double raise) { salary += raise; } public Person(String name, double salary) { this.name = name; this.salary = salary; } } public class ReflectionDemo2 { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Person p = new Person("蕭蕭弈寒", 100); // 獲取getName方法物件 Method m1 = p.getClass().getMethod("getName"); // 呼叫getName方法 String name = (String)m1.invoke(p); System.out.println("我的名字是:" + name); // 獲取raiseSalary方法物件,需要傳double型別的引數 Method m2 = p.getClass().getMethod("raiseSalary", double.class); // 呼叫raiseSalary方法,並傳入引數值 m2.invoke(p, 200); System.out.println("加薪後:salary=" + p.getSalary()); } }
【執行結果】:
我的名字是:蕭蕭弈寒
加薪後:salary=300.0
對於靜態方法,invoke的第一個引數可以被忽略,既可以將它設定為null。
package com.xiaoxiaoyihan.reflection; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; class Person { public static void speak(String name) { System.out.println("木頭!" + name + ",你倒是說話啊!"); } } public class ReflectionDemo2 { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Method m = Person.class.getMethod("speak", String.class); m.invoke(null, "蕭蕭弈寒"); } }
【執行結果】: 木頭!蕭蕭弈寒,你倒是說話啊!
注意:invoke的引數和返回值必須是Object型別的。這就意味著必須進行多次的型別轉換。這樣做將會是編譯器錯過檢查程式碼的機會。此外,通過反射獲得方法指標的程式碼比直接呼叫方法效率低。