我們平常創建類的實例并調用類中成員需要建立在一個前提下,就是已經知道類名和類中成員的信息,靈活性大大降低。甚至在一些項目中還需要修改源碼來滿足使用條件,大大降低了操作的靈活性。
Java 反射(Reflection)是 Java 語言的一個重要特性,它允許程序在運行時而不是編譯時獲取類中成員,并且可以動態地操作這些類的成員。
在講反射之前,我們先聊一聊反射機制中必不可少的一環:配置文件。我需要提前將我的類名,成員名填寫到配置文件中去。這樣在調用時就可以直接讀取配置文件中的信息。相較于讀取普通文件,配置文件的讀取和調用更加簡單。
一、讀取配置文件:
1.創建配置文件:在指定的路徑下(通常我放在src目錄下,不為別的,就因為路徑名簡單)創建一個以“.properties”為結尾的File文件(普通的鍵值對形式)作為配置文件。
2.填寫配置文件內容:以“鍵=值”的形式填寫,不必添加空格和引號,調用時通過鍵的字符串形式調用(后面會舉例說明)。
3.創建配置文件對象:首先new一個配置文件的對象,使用load方法將配置文件中的內容讀取到配置文件對象中。
load方法提供了兩種重載,既可以通過字節流輸入,也可以通過字符流輸入。推薦使用字節流輸入,配置文件通常為全英文形式,字節流輸入效率高。
4.獲取配置文件中的內容:Properties類提供了一種getProperty(鍵)方法來獲取配置文件中的信息,需要在方法中寫入一個鍵,就會返回一個String類型鍵對應的值。這個String類型的值就是我們要從配置文件中拿到的值。
Exam、我提前在src目錄下創建了一個名為“p.properties”的配置文件并提前寫好鍵值對來表示需要需要反射文件的路徑、屬性和方法。路徑表示com.reflectNewEdu包下的名為“reflectEdu”的文件,我需要反射這個文件中的成員。
//配置文件的內容
//路徑
className = com.reflectNewEdu.reflectEdu
//屬性
ObjName = str
//方法
methodName = printContent
methodName1 = printSome
Exam、在reflectEdu文件中需要寫一些成員來幫助我們進行實驗。
public class reflectEdu {//分別一個public和一個private屬性public int num = 123;private String str = "字符串";//方法public void printContent() {System.out.println("輸出內容");}private void printSome() {System.out.println("輸出一些東西");}
}
Exam、創建一個Test類,在Test類中填寫main方法具體實現反射:
二、獲取類對象(main函數中):
1.首先按照上面的步驟創建配置文件對象,并使用字節流加載配置文件進配置文件對象中。
Properties properties = new Properties();
properties.load(new FileReader("src/p.properties"));
2.為了繞過對象階段創建類對象,Class.forName(類全名)提供了一種在硬盤階段獲取類中成員的方法。Class.forName(類全名) ?的核心作用,是通過類的全限定名,讓JVM加載這個類,并返回一個代表該類“模板”的 Class ?對象。此時僅完成了“類的加載”,并沒有創建任何屬于這個類的具體對象,就像拿到了一張“汽車設計圖紙”,但還沒造出真正能開的汽車。
該方法需要寫入一個String類型的類全名,我們通過getProperty(鍵)方法從配置文件中拿到這個類全名。
Class.forName()方法返回一個Class類型的對象,這是一個類對象。類對象并不是哪個類的實例,但可以通過這個類對象下的方法獲取到類中的成員,甚至是獲取類的實例。(還是挺抽象的)
Class class0 = Class.forName(properties.getProperty("className"));
三、獲取類對象中的成員:
一、屬性(返回Field類型,需要import java.lang.reflect.Field;):
? ? ? ? 1.獲取當前類里所有屬性:getDeclaredFields(); (返回數組)
? ? ? ? 2.通過屬性名獲取屬性:getDeclaredField(屬性名);
? ? ? ? 3.獲取所有公共類型屬性:getFields();(返回數組)
? ? ? ? 4.獲取公共類型中指定的屬性:getField(屬性名);
? ? ? ? 5.設置對象的屬性:屬性對象.set(對象名,屬性值)
? ? ? ? 當屬性被private修飾無法被調用時,可以使用:類對象名.setAccessible(true); 來進行暴力反射。
二、方法(返回Method類型,需要import java.lang.reflect.Method;):
? ? ? ? 1.獲取類里所有方法:getDeclaredMethods();(返回數組)
? ? ? ? 2.通過方法名獲取方法:getDeclaredMethod(方法名);
? ? ? ? 3.獲取所有公共類型方法:getMethods();(返回數組)
? ? ? ? 4.獲取公共類型中指定的方法:getMethod(方法名);
? ? ? ? 5.運行指定的方法:方法對象.invoke(對象名, 參數);?
? ? ? ? 第一個為對象名,第二個為參數名,如果沒有參數可以不用輸入,允許暴力反射。
三、構造方法(返回Constructor類型,需要import java.lang.reflect.Constructor;):
? ? ? ? 1.獲取類里所有構造方法:getDeclaredConstructors();(返回數組)
? ? ? ? 2.獲取指定的無參構造方法:getDeclaredConstructor();
? ? ? ? 3.獲取指定的帶有參數的構造方法(舉例:帶有String和int類型的兩個參數的構造方法):getDeclaredConstructor(String.class,int.class);
? ? ? ? 4.獲取所有公共類型的構造方法:getConstructors();(返回數組)
? ? ? ? 5.獲取公共類型中指定的一個方法(有參或無參):getConstructor();
? ? ? ? 6.執行無參構造方法:Object 新變量名 = 構造方法對象/類對象名.newInstance(); 相當于Object obj = new ();?
? ? ? ? 7.執行有參的構造方法:Object 新變量名 = 構造方法對象.newInstance(參數);
需要注意我標藍的地方,都需要傳入一個類對象,那么如何獲取類的實例對象呢?
四、獲取類的實例對象:
1.通過構造方法獲取:我們已經拿到指向我們需要反射的類的對象了,直接通過getDeclaredConstructor()方法獲取一個構造方法(我這里是無參的),再執行構造方法中的newInstance()方法不就行了嘛。
代碼:
Constructor con = class0.getConstructor();
Object object = con.newInstance();
注:我們提前無法預測給類起的名字是什么,所以需要用它們共同的父類Object對象來接受。
2.通過類對象獲取:對類對象直接使用newInstance()方法,這樣無需知道類名,就可以創建類的實例對象啦。
Object object = class0.newInstance();
有了類的實例對象,我們就可以使用上面標藍的方法了,比如調用類中的方法等。
由此,反射的整個過程就實現完了如果有什么地方不明白的話,可以私信討論( o >o)/。