java使用反射創(chuàng)建并操作對象的方法
Class 對象可以獲得該類里的方法(由 Method 對象表示)、構(gòu)造器(由 Constructor 對象表示)、成員變量(由 Field 對象表示),這三個類都位于 java.lang.reflect 包下,并實現(xiàn)了 java.lang.reflect.Member 接口。程序可以通過對象來執(zhí)行對應的方法,通過 Constructor 對象來調(diào)用對應的構(gòu)造器創(chuàng)建實例,能通過 Field 對象直接訪問并修改對象的成員變量值。
創(chuàng)建對象
通過反射來生成對象需要先使用 Class 對象獲取指定的 Constructor 對象,再調(diào)用 Constructor 對象的 newInstance() 方法來創(chuàng)建該 Class 對象對應類的實例。通過這種方式可以選擇使用指定的構(gòu)造器來創(chuàng)建實例。
在很多 Java EE 框架中都需要根據(jù)配置文件信息來創(chuàng)建 Java 對象,從配置文件讀取的只是某個類的字符串類名,程序需要根據(jù)該字符串來創(chuàng)建對應的實例,就必須使用反射。
下面程序就實現(xiàn)了一個簡單的對象池,該對象池會根據(jù)配置文件讀取 key-value 對,然后創(chuàng)建這些對象,并將這些對象放入一個 HashMap 中。
public class ObjectPoolFactory { // 定義一個對象池,前面是對象名,后面是實際對象 private Map<String, Object> objectPool = new HashMap<>(); // 定義一個創(chuàng)建對象的方法 // 該方法只要傳入一個字符串類名,程序可以根據(jù)該類名生成Java對象 private Object createObject(String clazzName) throws Exception, IllegalAccessException, ClassNotFoundException { // 根據(jù)字符串來獲取對應的Class對象 Class<?> clazz = Class.forName(clazzName); // 使用clazz對應類的默認構(gòu)造器創(chuàng)建實例 return clazz.getConstructor().newInstance(); } // 該方法根據(jù)指定文件來初始化對象池 // 它會根據(jù)配置文件來創(chuàng)建對象 public void initPool(String fileName) throws InstantiationException, IllegalAccessException, ClassNotFoundException { try (FileInputStream fis = new FileInputStream(fileName)) { Properties props = new Properties(); props.load(fis); for (String name : props.stringPropertyNames()) { // 每取出一對key-value對,就根據(jù)value創(chuàng)建一個對象 // 調(diào)用createObject()創(chuàng)建對象,并將對象添加到對象池中 objectPool.put(name, createObject(props.getProperty(name))); } } catch (Exception ex) { System.out.println('讀取' + fileName + '異常'); } } public Object getObject(String name) { // 從objectPool中取出指定name對應的對象 return objectPool.get(name); } public static void main(String[] args) throws Exception { ObjectPoolFactory pf = new ObjectPoolFactory(); pf.initPool('obj.txt'); System.out.println(pf.getObject('a')); // ① System.out.println(pf.getObject('b')); // ② }}
上面程序中 createObject() 方法里的兩行粗體字代碼就是根據(jù)字符串來創(chuàng)建 Java 對象的關(guān)鍵代碼,程序調(diào)用 Class 對象的 newInstance() 方法即可創(chuàng)建一個 Java 對象。程序中的 initPool() 方法會讀取屬性文件,對屬性文件中每個 key-value 對創(chuàng)建一個 Java 對象,其中 value 是該 Java 對象的實現(xiàn)類,而 key 是該 Java 對象放入對象池中的名字。為該程序提供如下屬性配置文件。
a=java.util.Dateb=javax.swing.JFrame
編譯、運行上面的 ObjectPoolFactory 程序,執(zhí)行到 main 方法中的①號代碼處,將看到輸出系統(tǒng)當前時間——這表明對象池中已經(jīng)有了一個名為a的對象,該對象是一個 java.util.Date 對象。執(zhí)行到②號代碼處,將看到輸出一個 JFrame 對象。
提示:這種使用配置文件來配置對象,然后由程序根據(jù)配置文件來創(chuàng)建對象的方式非常有用,大名鼎鼎的 Spring 框架就采用這種方式大大簡化了 Java EE 應用的開發(fā)。當然,Spring 采用的是 XML 配置文件——畢竟屬性文件能配置的信息太有限了,而 XML 配置文件能配置的信息就豐富多。
如果不想利用默認構(gòu)造器來創(chuàng)建 Java 對象,而想利用指定的構(gòu)造器來創(chuàng)建 Java 對象,則需要利用 Constructor 對象,每個 Constructor 對應一個構(gòu)造器。為了利用指定的構(gòu)造器來創(chuàng)建 Java 對象,需要如下三個步驟。
獲取該類的 Class 對象。 利用 Class 對象的 getConstructor() 方法來獲取指定的構(gòu)造器。 調(diào)用 Constructor 的 newInstance() 方法來創(chuàng)建 Java 對象。下面程序利用反射來創(chuàng)建一個 JFrame 對象,而且使用指定的構(gòu)造器。
public class CreateJFrame { public static void main(String[] args) throws Exception { // 獲取JFrame對應的Class對象 Class<?> jframeClazz = Class.forName('javax.swing.JFrame'); // 獲取JFrame中帶一個字符串參數(shù)的構(gòu)造器 Constructor ctor = jframeClazz.getConstructor(String.class); // 調(diào)用Constructor的newInstance方法創(chuàng)建對象 Object obj = ctor.newInstance('測試窗口'); // 輸出JFrame對象 System.out.println(obj); }}
上面程序中第一行粗休字代碼用于獲取 JFrame 類的指定構(gòu)造器,前面已經(jīng)提到:如果要唯一地確定某類中的構(gòu)造器,只要指定構(gòu)造器的形參列表即可。第一行粗體字代碼獲取構(gòu)造器時傳入了一個 String 類型,即表明想獲取只有一個字符串參數(shù)的構(gòu)造器。
程序中第二行粗體字代碼使用指定構(gòu)造器的 newInstance() 方法來創(chuàng)建一個 Java 對象,當調(diào)用 Constructor 對象的 newInstance() 方法時通常需要傳入?yún)?shù),因為調(diào)用 Constructor 的 newInstance() 方法實際上等于調(diào)用它對應的構(gòu)造器,傳給 newInstance() 方法的參數(shù)將作為對應構(gòu)造器的參數(shù)。
對于上面的 CreateFrame.java 中已知 java.swing.JFrame 類的情形,通常沒有必要使用反射來創(chuàng)建該對象,畢竟通過反射創(chuàng)建對象時性能要稍低一些。實際上,只有當程序需要動態(tài)創(chuàng)建某個類的對象時才會考慮使用反射,通常在開發(fā)通用性比較廣的框架、基礎平臺時可能會大量使用反射。
調(diào)用方法
當獲得某個類對應的 Class 對象后,就可以通過該 Class 對象的 getMethods() 方法或者 getMethod()方法來獲取全部方法或指定方法——這兩個方法的返回值是 Method 數(shù)組,或者 Method 對象。
每個 Method 對象對應一個方法,獲得 Method 對象后,程序就可通過該 Method 來調(diào)用它對應的方法。在 Method 里包含一個 Invoke() 方法,該方法的簽名如下。
Object invoke(Object obj, Object...args):該方法中的 obj 是執(zhí)行該方法的主調(diào),后面的 args 是執(zhí)行該方法時傳入該方法的實參。下面程序?qū)η懊娴膶ο蟪毓S進行加強,允許在配置文件中增加配置對象的成員變量的值,對象池工廠會讀取為該對象配置的成員變量值,并利用該對象對應的 setter 方法設置成員變量的值。
public class ExtendedObjectPoolFactory { // 定義一個對象池,前面是對象名,后面是實際對象 private Map<String, Object> objectPool = new HashMap<>(); private Properties config = new Properties(); // 從指定屬性文件中初始化Properties對象 public void init(String fileName) { try (FileInputStream fis = new FileInputStream(fileName)) { config.load(fis); } catch (IOException ex) { System.out.println('讀取' + fileName + '異常'); } } // 定義一個創(chuàng)建對象的方法 // 該方法只要傳入一個字符串類名,程序可以根據(jù)該類名生成Java對象 private Object createObject(String clazzName) throws Exception { // 根據(jù)字符串來獲取對應的Class對象 Class<?> clazz = Class.forName(clazzName); // 使用clazz對應類的默認構(gòu)造器創(chuàng)建實例 return clazz.getConstructor().newInstance(); } // 該方法根據(jù)指定文件來初始化對象池 // 它會根據(jù)配置文件來創(chuàng)建對象 public void initPool() throws Exception { for (String name : config.stringPropertyNames()) { // 每取出一個key-value對,如果key中不包含百分號(%) // 這就表明是根據(jù)value來創(chuàng)建一個對象 // 調(diào)用createObject創(chuàng)建對象,并將對象添加到對象池中 if (!name.contains('%')) { objectPool.put(name, createObject(config.getProperty(name))); } } } // 該方法將會根據(jù)屬性文件來調(diào)用指定對象的setter方法 public void initProperty() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { for (String name : config.stringPropertyNames()) { // 每取出一對key-value對,如果key中包含百分號(%) // 即可認為該key用于控制調(diào)用對象的setter方法設置值 // %前半為對象名字,后半控制setter方法名 if (name.contains('%')) { // 將配置文件中的key按%分割 String[] objAndProp = name.split('%'); // 取出調(diào)用setter方法的參數(shù)值 Object target = getObject(objAndProp[0]); // 獲取setter方法名:set + '首字母大寫' + 剩下部分 String mtdName = 'set' + objAndProp[1].substring(0, 1).toUpperCase() + objAndProp[1].substring(1); // 通過target的getClass()獲取它的實現(xiàn)類所對應的Class對象 Class<?> targetClass = target.getClass(); // 獲取希望調(diào)用的setter方法 Method mtd = targetClass.getMethod(mtdName, String.class); // 通過Method的invoke方法執(zhí)行setter方法 // 將config.getProperty(name)的值作為調(diào)用setter方法的參數(shù) mtd.invoke(target, config.getProperty(name)); } } } public Object getObject(String name) { // 從objectPool中取出指定name對應的對象 return objectPool.get(name); } public static void main(String[] args) throws Exception { ExtendedObjectPoolFactory epf = new ExtendedObjectPoolFactory(); epf.init('extObj.txt'); epf.initPool(); epf.initProperty(); System.out.println(epf.getObject('a')); }}
上面程序中 initProperty() 方法里的第一行粗體字代碼獲取目標類中包含一個 String 參數(shù)的 setter 方法,第二行粗體字代碼通過調(diào)用 Method 的 invoke() 方法來執(zhí)行該 setter 方法,該方法執(zhí)行完成后,就相當于執(zhí)行了目標對象的 setter 方法。為上面程序提供如下配置文件。
a=javax.swing.JFrameb=javax.swing.JLabel#set the title of aa%title=Test Title
上面配置文件中的 a%title 行表明希望調(diào)用a對象的 setTitle() 方法,調(diào)用該方法的參數(shù)值為 Test Title。編譯、運行上面的 ExtendedObjectPoolFactory.java 程序,可以看到輸出一個 JFrame 窗口,該窗口的標題為 Test Title。
提示:Spring 框架就是通過這種方式將成員變量值以及依賴對象等都放在配置文件中進行管理的,從而實現(xiàn)了較好的解耦。這也是 Spring 框架的 IOC 的秘密。
當通過 Method 的 invoke() 方法來調(diào)用對應的方法時,Java 會要求程序必須有調(diào)用該方法的權(quán)限。如果程序確實需要調(diào)用某個對象的 private 方法,則可以先調(diào)用 Method 對象的如下方法。
setAccessible(boolean flag):將 Method 對象的 accessible 設置為指定的布爾值。值為true,指示該 Method 在使用時應該取消 Java 語言的訪問權(quán)限檢查:值為false,則指示該 Method 在使用時要實施 Java 語言的訪問權(quán)限檢查。注意:實際上,setAccessible() 方法并不屬于 Method,而是屬于它的父類 AccessibleObject。因此 Method、Constructor、Field 都可調(diào)用該方法,從而實現(xiàn)通過反射來調(diào)用 private 方法、private 構(gòu)造器和成員變量,下一節(jié)將會讓讀者看到這種示例。也就是說,它們可以通過調(diào)用該方法來取消訪問權(quán)限檢查,通過反射即可訪問 private 成員。
訪問成員變量值
通過 Class 對象的 getFields() 或 getField() 方法可以獲取該類所包括的全部成員變量或指定成員變量。Field 提供了如下兩組方法來讀取或設置成員變量值。
getXxx(Object obj):獲取 obj 對象的該成員變量的值。此處的 Xxx 對應8種基本類型,如果該成員變量的類型是引用類型,則取消 get 后面的Xxx。 setXxx(Object obj, Xxx val):將 obj 對象的該成員變量設置成值。此處的 Xxx 對應8種基本類型,如果該成員變量的類型是引用類型,則取消 set 后面的Xxx。使用這兩個方法可以隨意地訪問指定對象的所有成員變量,包括 private 修飾的成員變量。
class Person { private String name; private int age; public String toString() { return 'Person[name:' + name + ' , age:' + age + ' ]'; }}public class FieldTest { public static void main(String[] args) throws Exception { // 創(chuàng)建一個Person對象 Person p = new Person(); // 獲取Person類對應的Class對象 Class<Person> personClazz = Person.class; // 獲取Person的名為name的成員變量 // 使用getDeclaredField()方法表明可獲取各種訪問控制符的成員變量 Field nameField = personClazz.getDeclaredField('name'); // 設置通過反射訪問該成員變量時取消訪問權(quán)限檢查 nameField.setAccessible(true); // 調(diào)用set()方法為p對象的name成員變量設置值 nameField.set(p, 'Yeeku.H.Lee'); // 獲取Person類名為age的成員變量 Field ageField = personClazz.getDeclaredField('age'); // 設置通過反射訪問該成員變量時取消訪問權(quán)限檢查 ageField.setAccessible(true); // 調(diào)用setInt()方法為p對象的age成員變量設置值 ageField.setInt(p, 30); System.out.println(p); }}
上面程序中先定義了一個 Person 類,該類里包含兩個 private 成員變量:name 和 age,在通常情況下,這兩個成員變量只能在 Person 類里訪問。但本程序 FieldTest 的 main() 方法中6行粗體字代碼通過反射修改了 Person 對象的 name、age 兩個成員變量的值。
第一行粗體字代碼使用 getDeclaredField() 方法獲取了名為 name 的成員變量,注意此處不是使用 getField()方法,因為 getField() 方法只能獲取 public 訪問控制的成員變量,而 getDeclaredField() 方法則可以獲取所有的成員變量;第二行粗體字代碼則通過反射訪問該成員變量時不受訪問權(quán)限的控制;第三行粗體字代碼修改了 Person 對象的 name 成員變量的值。修改 Person 對象的 age 成員變量的值的方式與此完全相同。
編譯、運行上面程序,會看到如下輸出:
Person[name:Yeeku.H.Lee , age:30 ]
操作數(shù)組
在 java.lang.reflect 包下還提供了一個 Array 類,Array 對象可以代表所有的數(shù)組。程序可以通過使用 Array 來動態(tài)地創(chuàng)建數(shù)組,操作數(shù)組元素等。
Array 提供了如下幾類方法。
static Object newInstance(Class<?> componentType,int...length):創(chuàng)建一個具有指定的元素類型、指定維度的新數(shù)組。 static xxx getXxx(Object array, int index):返回 array 數(shù)組中第 index 個元素。其中是各種基本數(shù)據(jù)類型,如果數(shù)組元素是引用類型,則該方法變?yōu)?get(Object array, int index)。 static void setXxx(Object array, int index, xxx val):將 array 數(shù)組中第 index 個元素的值設為 val。其中 xxx 是各種基本數(shù)據(jù)類型,如果數(shù)組元素是引用類型,則該方法變成 set(Object array, int index, Object val)。下面程序示范了如何使用 Array 來生成數(shù)組,為指定數(shù)組元素賦值,并獲取指定數(shù)組元素的方式。
public class ArrayTest1 { public static void main(String args[]) { try { // 創(chuàng)建一個元素類型為String ,長度為10的數(shù)組 Object arr = Array.newInstance(String.class, 10); // 依次為arr數(shù)組中index為5、6的元素賦值 Array.set(arr, 5, '瘋狂Java講義'); Array.set(arr, 6, '輕量級Java EE企業(yè)應用實戰(zhàn)'); // 依次取出arr數(shù)組中index為5、6的元素的值 Object book1 = Array.get(arr, 5); Object book2 = Array.get(arr, 6); // 輸出arr數(shù)組中index為5、6的元素 System.out.println(book1); System.out.println(book2); } catch (Throwable e) { System.err.println(e); } }}
上面程序中三行粗體字代碼分別是通過 Array 創(chuàng)建數(shù)組,為數(shù)組元素設置值,訪問數(shù)組元素的值的示例代碼,程序通過使用 Array 就可以動態(tài)地創(chuàng)建并操作數(shù)組。
下面程序比上面程序稍微復雜一點,下面程序使用 Array 類創(chuàng)建了一個三維數(shù)組。
public class ArrayTest2 { public static void main(String args[]) { /* * 創(chuàng)建一個三維數(shù)組。 根據(jù)前面介紹數(shù)組時講的:三維數(shù)組也是一維數(shù)組, 是數(shù)組元素是二維數(shù)組的一維數(shù)組, * 因此可以認為arr是長度為3的一維數(shù)組 */ Object arr = Array.newInstance(String.class, 3, 4, 10); // 獲取arr數(shù)組中index為2的元素,該元素應該是二維數(shù)組 Object arrObj = Array.get(arr, 2); // 使用Array為二維數(shù)組的數(shù)組元素賦值。二維數(shù)組的數(shù)組元素是一維數(shù)組, // 所以傳入Array的set()方法的第三個參數(shù)是一維數(shù)組。 Array.set(arrObj, 2, new String[] { '瘋狂Java講義', '輕量級Java EE企業(yè)應用實戰(zhàn)' }); // 獲取arrObj數(shù)組中index為3的元素,該元素應該是一維數(shù)組。 Object anArr = Array.get(arrObj, 3); Array.set(anArr, 8, '瘋狂Android講義'); // 將arr強制類型轉(zhuǎn)換為三維數(shù)組 String[][][] cast = (String[][][]) arr; // 獲取cast三維數(shù)組中指定元素的值 System.out.println(cast[2][3][8]); System.out.println(cast[2][2][0]); System.out.println(cast[2][2][1]); }}
上面程序的第一行粗體字代碼使用 Array 創(chuàng)建了一個三維數(shù)組,程序中較難理解的地方是第二段粗體字代碼部分,使用 Array 為 arrObj 的指定元素賦值,相當于為二維數(shù)組的元素賦值。由于二維數(shù)組的元素是一維數(shù)組,所以程序傳入的參數(shù)是一個一維數(shù)組對象。
運行上面程序,將看到 cast[2][3][8]、cast[2][2][0]、cast[2][2][1] 元素都有值,這些值就是剛才程序通過反射傳入的數(shù)組元素值。
以上就是java使用反射創(chuàng)建并操作對象的方法的詳細內(nèi)容,更多關(guān)于JAVA 反射創(chuàng)建并操作對象的資料請關(guān)注好吧啦網(wǎng)其它相關(guān)文章!
相關(guān)文章:
1. Intellij IDEA連接Navicat數(shù)據(jù)庫的方法2. 關(guān)于Mysql-connector-java驅(qū)動版本問題總結(jié)3. 使用css實現(xiàn)全兼容tooltip提示框4. CSS自定義滾動條樣式案例詳解5. 詳解JavaScript作用域、作用域鏈和閉包的用法6. python 批量下載bilibili視頻的gui程序7. python中HTMLParser模塊知識點總結(jié)8. 通過工廠模式返回Spring Bean方法解析9. Ajax提交post請求案例分析10. JSP實現(xiàn)客戶信息管理系統(tǒng)
