反射機制
JAVA中反射是動態獲取信息以及動態調用對象方法的一種反射機制。
Java反射就是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意方法和屬性;並且能改變它的屬性。而這也是Java被視爲動態語言的一個關鍵性質。Java反射的功能是在運行時判斷任意一個對象所屬的類,在運行時構造任意一個類的對象,在運行時判斷任意一個類所具有的成員變量和方法,在運行時調用任意一個對象的方法,生成動態代理。反射是Java中最爲重要的特性,幾乎所有的開發框架以及應用技術都是基於反射技術的應用。
認識反射
在一個程序中如果要通過對象取得這個對象所在類的信息,可以通過Object類中的getClass()方法(public final Class<?>getClass())實現。
例:反射初步操作
package Project.Study.Reflect;
import java.util.Date;
public class Test1 {
public static void main(String[]args){
Date date=new Date(); //產生實例化對象
System.out.println(date.getClass());//直接反射輸出對應類的信息
}
}
//結果:
//class java.util.Date
Class類對象實例化
當我們使用getClass()方法時,返回的類型爲java.lang.Class,這是反射操作的源頭類,即所有的反射操作都需要從此類開始,而最關鍵的是這個類有以下3種實例化方式。
一.調用Object類中的getClass()方法,但是如果要使用此類操作必須要有實例化對象。
該方法往往出現在簡單Java類與提交參數的自動賦值操作中,像Struts、Spring MVC都會提供表單參數與簡單Java類的自動轉換。
例:
package Project.Study.Reflect;
import java.util.Date;
public class Test2 {
public static void main(String[]args){
Date date=new Date(); //產生實例化對象
Class<?>cls=date.getClass(); //通過實例化對象取得Class對象
//Class類中定義有“public String getName()”方法可以取得類的完整名稱
System.out.println(cls.getName()); //直接對象所在類的名稱
}
}
//結果:
//java.util.Date
二.使用“類.class”取得,此時可以不需要通過指定類的實例化對象取得
該方式往往是將反射操作的類型設置交由用戶使用,像Hibernate中進行數據保存以及根據ID查詢中會使用到此類操作。
例:
package Project.Study.Reflect;
public class Test3 {
public static void main(String[]args){
Class<?>cls=java.util.Date.class; //通過類名稱取得Class類對象
System.out.println(cls.getName()); //直接對象所在類的名稱
}
}
//結果:
//java.util.Date
三.調用Class類提供的方法:public static Class<?>forName(String className)throws ClassNotFoundException。
該方法可以實現配置文件以及Annotation配置的反射操作,幾乎所有的開發框架都是依靠此方式實現的。
例:
package Project.Study.Reflect;
public class Test4 {
public static void main(String[]args) throws Exception {
//此處直接傳遞了一個要進行反射操作類的完整名稱,是用字符串定義的
Class<?>cls=Class.forName("java.util.Date");
System.out.println(cls.getName()); //直接對象所在類的名稱
}
}
//結果:
//java.util.Date
反射實例化對象
掌握了Class類對象實例化的三種操作方式,我們現在就可以利用Class類來進行類的反射控制了。
Class類的常用方法
No. | 方法 | 類型 | 描述 |
---|---|---|---|
1 | public static Class<?>forName(String className) throws ClassNotFoundException | 普通 | 通過字符串設置的類名稱實例化Class類對象 |
2 | public Class<?>[] getInterfaces() | 普通 | 取得類實現的所有接口 |
3 | public String getName() | 普通 | 取得反射操作類的全名 |
4 | public String getSimpleName() | 普通 | 取得反射操作類名,不包括包名稱 |
5 | public Package getPackage() | 普通 | 取得反射操作操作類所在的包 |
6 | public Class<? super T>getSuperclass() | 普通 | 取得反射操作類的父類 |
7 | public boolean isEnum() | 普通 | 反射操作的類是否是枚舉 |
8 | public boolean isInterface() | 普通 | 反射操作的類是否是接口 |
9 | public boolean isArray() | 普通 | 反射操作的類是否是數組 |
10 | public T newInstance() throws InstantiationException,IllegalAccessException | 普通 | 反射實例化對象 |
在Class類中最重要的一個方法就是newInstance()方法,通過此方法可以利用反射實現Class類包裝類型的對象實例化操作,也就是說即使不使用關鍵字new也可以進行對象的實例化操作。
注意:如果利用newInstance()方法反射實例化對象,則類中一定要提供無參構造方法,否則會出現語法錯誤。
例:利用反射實例化對象
package Project.Study.Reflect;
class Book{
public Book(){
System.out.println("該類的無參方法");
}
@Override
public String toString(){
return "Hello World";
}
}
public class Test5 {
public static void main(String[]args) throws Exception {
Class<?>cls=Class.forName("Project.Study.Reflect.Book");//設置要操作對象的類名稱
//反射實例化後的對象返回的結果都是Object類型
Object object=cls.newInstance(); //相當於使用new調用無參結構
Book book=(Book)object; //向下轉型
System.out.println(book);
}
}
//結果:
//該類的無參方法
//Hello World
上程序中的newInstance()方法返回的類型爲Object,如果有需要則可以利用對象的向下轉型操作將其強制變爲子類實例進行操作。
可能會有人問使用newInstance()方法進行對象的實例化有什麼好處,直接用new它不香嗎?
其實因爲關鍵字new實例化對象需要明確地指定類的構造方法,故new是造成耦合的最大元兇,所以使用newInstance()方法實例化對象可以實現更好的解耦合操作。
使用反射調用構造
使用Class類中的newInstance()方法雖然可以實現反射實例化對象操作,但是這樣的操作本身有一個限制,就是操作對象的類中必須提供無參的構造方法。
例:錯誤示例
package Project.Study.Reflect;
class Book2{
private String title;
private double price;
public Book2(String title,double price){
this.price=price;
this.title=title;
}
@Override
public String toString(){
return "圖書名稱:"+this.title+",價格:"+this.price;
}
}
public class Test6 {
public static void main(String[]args) throws Exception {
Class<?>cls=Class.forName("Project.Study.Reflect.Book2");
Object object=cls.newInstance();
System.out.println(object);
}
}
//結果:
//Exception in thread "main" java.lang.InstantiationException: Project.Study.Reflect.Book2
// at java.base/java.lang.Class.newInstance(Class.java:585)
// at Project.Study.Reflect.Test6.main(Test6.java:18)
//Caused by: java.lang.NoSuchMethodException: Project.Study.Reflect.Book2.<init>()
// at java.base/java.lang.Class.getConstructor0(Class.java:3350)
// at java.base/java.lang.Class.newInstance(Class.java:572)
// ... 1 more
所以,當類中沒有提供無參構造方法的時候,就必須通過java.lang.reflect.Constructor類來實現對象的反射實例化操作。(java.lang.reflect是所有反射操作類的程序包)
取得類中的構造方法
No. | 方法 | 類型 | 描述 |
---|---|---|---|
1 | public Constructor<?>[] getConstructors() throws SecurityException | 普通 | 取得全部的構造方法 |
2 | public Constructor< T >[] getConstructors(Class<?>…parameterTypes) throws NoSuchMethodException, SecurityException | 普通 | 取得指定參數類型的構造方法 |
Constructor類的常用操作方法
No. | 方法 | 類型 | 描述 |
---|---|---|---|
1 | public Class<?>[] getExceptionTypes() | 普通 | 返回構造方法上所有拋出異常的類型 |
2 | public int getModifiers() | 普通 | 取得構造方法的修飾符 |
3 | public String getName() | 普通 | 取得構造方法的名字 |
4 | public int getParameterCount() | 普通 | 取得構造方法中的參數個數 |
5 | public Class <?>[] getParameterTypes() | 普通 | 取得構造方法中的參數類型 |
6 | public T newInstance(Object…initargs) throws InstantiationException,IllegalAccessException,IllegalArgumentException,InvocationTargetException | 普通 | 調用指定參數的構造實例化對象 |
注意:因爲修飾符是用數字描述的,所有的修飾符都是一個數字,所以取得修飾符的方法返回的是int類型。
例:明確調用類中的有參構造
package Project.Study.Reflect;
import java.lang.reflect.Constructor;
class Book3{
private String title;
private double price;
public Book3(String title,double price){
this.title=title;
this.price=price;
}
@Override
public String toString(){
return "圖書名稱:"+this.title+",價格:"+this.price;
}
}
public class Test6 {
public static void main(String[]args)throws Exception{
Class<?>cls=Class.forName("Project.Study.Reflect.Book3");
//明確地找到Book3類中兩個參數的構造,第一個參數類型是String,第二個是double
Constructor<?>constructor=cls.getConstructor(String.class,double.class);
Object object=constructor.newInstance("Java",79.9); //實例化對象
System.out.println(object);
}
}
//結果:
//圖書名稱:Java,價格:79.9
上程序的思路:
1.首先利用Class類對象取得此構造方法(返回Constructor類對象);
2.然後利用Constructor類中的newInstance()方法傳遞指定的數據,就可以調用有參構造進行對象的反射實例化。
反射調用方法
Class類取得普通方法的操作
No. | 方法 | 類型 | 描述 |
---|---|---|---|
1 | public Method[] getMethods() throws SecurityException | 普通 | 取得類中的全部方法 |
2 | public Method[] getMethod(String name,Class<?>…parameterTypes) throws NoSuchMethodException,SecurityException | 普通 | 取得類中指定方法名稱與參數類型的方法 |
注:反射操作中,每一個方法都通過java.lang.reflect.Method類表示。
Method類的常用方法
No. | 方法 | 類型 | 描述 |
---|---|---|---|
1 | public int getModifiers() | 普通 | 取得方法的修飾符 |
2 | public Class<?> getReturnType() | 普通 | 取得方法的返回值類型 |
3 | public int getParameterCount() | 普通 | 取得方法中定義的參數數量 |
4 | public Class<?> getParameterTypes() | 普通 | 取得方法中定義的所有參數類型 |
5 | public Object invoke(Object obj,Object…args) throws IllegalAccessException,IllegalArgumentException,InvocationTargetException | 普通 | 反射調用方法並且傳遞執行方法所需要的參數數據 |
6 | public Class<?> getExceptionTypes() | 普通 | 取得方法拋出的異常類型 |
例:使用反射操作簡單Java類的屬性
package Project.Study.Reflect;
import java.lang.reflect.Method;
class Book4{
private String title;
public void setTitle(String title){
this.title=title;
}
public String getTitle() {
return title;
}
}
public class Test7 {
public static void main(String[]args)throws Exception{
String fieldName="Title";
Class<?>cls=Class.forName("Project.Study.Reflect.Book4");
Object object=cls.newInstance();
//取得類中的setTitle()方法
Method setMet=cls.getMethod("set"+fieldName,String.class);
//取得類中的getTitle()方法,本方法不接收參數並且沒有返回值類型說明
Method getMet=cls.getMethod("get"+fieldName);
setMet.invoke(object,"Java書"); //等價於Book4類對象.setTitle("Java書");
System.out.println(getMet.invoke(object)); //等價於Book4類對象.getTitle();
}
}
//結果:
//Java書
反射調用成員
Class類中取得成員的操作
No. | 方法 | 類型 | 描述 |
---|---|---|---|
1 | public Field[] getDeclaredFields() throws SecurityException | 普通 | 取得本類定義的全部成員 |
2 | public Field[] getDeclaredFields(String name) throws NoSuchFieldException,SecurityException | 普通 | 取得本類指定名稱的成員 |
3 | public Field[] getFields() throws SecurityException | 普通 | 取得本類繼承父類的全部成員 |
4 | public Field[] getField(String name) throws NoSuchFieldException,SecurityException | 普通 | 取得本類繼承父類中指定名稱的成員 |
Field類的常用方法
No. | 方法 | 類型 | 描述 |
---|---|---|---|
1 | public Class<?> getType() | 普通 | 取得改成員的類型 |
2 | public Object get(Object obj) throws IllegalAccessException,IllegalArgumentException | 普通 | 取得指定對象中的成員的內容,相當於直接調用成員 |
3 | public void set(Object obj,Object value) throws IllegalAccessException,IllegalArgumentException | 普通 | 設置指定對象中的成員內容,相當於直接利用對象調用成員設置內容 |
注意:不管使用反射調用的是普通方法還是調用類中的成員,都必須存在實例化對象(可以依靠反射取得實例化對象),因爲類中的屬性必須在類產生實例化對象後纔可以使用。
例:利用反射直接操作私有成員
package Project.Study.Reflect;
import java.lang.reflect.Field;
class Book5{
private String title;
}
public class Test8 {
public static void main(String[]args)throws Exception{
Class<?>cls=Class.forName("Project.Study.Reflect.Book5"); //取得反射對象
Object object=cls.newInstance(); //必須給出實例化對象
Field titleField=cls.getDeclaredField("title"); //取得類中的title屬性
titleField.setAccessible(true); //取消封裝,使title屬性可以被外界訪問
titleField.set(object,"Java書"); //相當於:Book5類對象.title="數據"
System.out.println(titleField.get(object)); //相當於:Book5類對象.title
}
}
//結果:
//Java書