歡迎您光臨本站 註冊首頁

java使用反射創建並操作對象的方法

←手機掃碼閱讀     sl_ivan @ 2020-06-21 , reply:0

Class 對象可以獲得該類裡的方法(由 Method 對象表示)、構造器(由 Constructor 對象表示)、成員變量(由 Field 對象表示),這三個類都位於 java.lang.reflect 包下,並實現了 java.lang.reflect.Member 接口。程序可以通過對象來執行對應的方法,通過 Constructor 對象來調用對應的構造器創建實例,能通過 Field 對象直接訪問並修改對象的成員變量值。

創建對象
 

通過反射來生成對象需要先使用 Class 對象獲取指定的 Constructor 對象,再調用 Constructor 對象的 newInstance() 方法來創建該 Class 對象對應類的實例。通過這種方式可以選擇使用指定的構造器來創建實例。

在很多 Java EE 框架中都需要根據配置文件信息來創建 Java 對象,從配置文件讀取的只是某個類的字符串類名,程序需要根據該字符串來創建對應的實例,就必須使用反射。

下面程序就實現了一個簡單的對象池,該對象池會根據配置文件讀取 key-value 對,然後創建這些對象,並將這些對象放入一個 HashMap 中。

  public class ObjectPoolFactory {   // 定義一個對象池,前面是對象名,後面是實際對象   private MapobjectPool = new HashMap<>();     // 定義一個創建對象的方法   // 該方法只要傳入一個字符串類名,程序可以根據該類名生成Java對象   private Object createObject(String clazzName) throws Exception, IllegalAccessException, ClassNotFoundException {   // 根據字符串來獲取對應的Class對象   Class clazz = Class.forName(clazzName);   // 使用clazz對應類的默認構造器創建實例   return clazz.getConstructor().newInstance();   }     // 該方法根據指定文件來初始化對象池   // 它會根據配置文件來創建對象   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對,就根據value創建一個對象   // 調用createObject()創建對象,並將對象添加到對象池中   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() 方法裡的兩行粗體字代碼就是根據字符串來創建 Java 對象的關鍵代碼,程序調用 Class 對象的 newInstance() 方法即可創建一個 Java 對象。程序中的 initPool() 方法會讀取屬性文件,對屬性文件中每個 key-value 對創建一個 Java 對象,其中 value 是該 Java 對象的實現類,而 key 是該 Java 對象放入對象池中的名字。為該程序提供如下屬性配置文件。

a=java.util.Date
 b=javax.swing.JFrame

編譯、運行上面的 ObjectPoolFactory 程序,執行到 main 方法中的①號代碼處,將看到輸出系統當前時間――這表明對象池中已經有了一個名為a的對象,該對象是一個 java.util.Date 對象。執行到②號代碼處,將看到輸出一個 JFrame 對象。

提示:這種使用配置文件來配置對象,然後由程序根據配置文件來創建對象的方式非常有用,大名鼎鼎的 Spring 框架就採用這種方式大大簡化了 Java EE 應用的開發。當然,Spring 採用的是 XML 配置文件――畢竟屬性文件能配置的信息太有限了,而 XML 配置文件能配置的信息就豐富多。

如果不想利用默認構造器來創建 Java 對象,而想利用指定的構造器來創建 Java 對象,則需要利用 Constructor 對象,每個 Constructor 對應一個構造器。為了利用指定的構造器來創建 Java 對象,需要如下三個步驟。

  1. 獲取該類的 Class 對象。

  2. 利用 Class 對象的 getConstructor() 方法來獲取指定的構造器。

  3. 調用 Constructor 的 newInstance() 方法來創建 Java 對象。

下面程序利用反射來創建一個 JFrame 對象,而且使用指定的構造器。

  public class CreateJFrame {   public static void main(String[] args) throws Exception {   // 獲取JFrame對應的Class對象   Class jframeClazz = Class.forName("javax.swing.JFrame");   // 獲取JFrame中帶一個字符串參數的構造器   Constructor ctor = jframeClazz.getConstructor(String.class);   // 調用Constructor的newInstance方法創建對象   Object obj = ctor.newInstance("測試窗口");   // 輸出JFrame對象   System.out.println(obj);   }  }

 

上面程序中第一行粗休字代碼用於獲取 JFrame 類的指定構造器,前面已經提到:如果要唯一地確定某類中的構造器,只要指定構造器的形參列表即可。第一行粗體字代碼獲取構造器時傳入了一個 String 類型,即表明想獲取只有一個字符串參數的構造器。

程序中第二行粗體字代碼使用指定構造器的 newInstance() 方法來創建一個 Java 對象,當調用 Constructor 對象的 newInstance() 方法時通常需要傳入參數,因為調用 Constructor 的 newInstance() 方法實際上等於調用它對應的構造器,傳給 newInstance() 方法的參數將作為對應構造器的參數。

對於上面的 CreateFrame.java 中已知 java.swing.JFrame 類的情形,通常沒有必要使用反射來創建該對象,畢竟通過反射創建對象時性能要稍低一些。實際上,只有當程序需要動態創建某個類的對象時才會考慮使用反射,通常在開發通用性比較廣的框架、基礎平臺時可能會大量使用反射。

調用方法
 

當獲得某個類對應的 Class 對象後,就可以通過該 Class 對象的 getMethods() 方法或者 getMethod()方法來獲取全部方法或指定方法――這兩個方法的返回值是 Method 數組,或者 Method 對象。

每個 Method 對象對應一個方法,獲得 Method 對象後,程序就可通過該 Method 來調用它對應的方法。在 Method 裡包含一個 Invoke() 方法,該方法的簽名如下。

  • Object invoke(Object obj, Object...args):該方法中的 obj 是執行該方法的主調,後面的 args 是執行該方法時傳入該方法的實參。

下面程序對前面的對象池工廠進行加強,允許在配置文件中增加配置對象的成員變量的值,對象池工廠會讀取為該對象配置的成員變量值,並利用該對象對應的 setter 方法設置成員變量的值。

  public class ExtendedObjectPoolFactory {   // 定義一個對象池,前面是對象名,後面是實際對象   private MapobjectPool = 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 + "異常");   }   }     // 定義一個創建對象的方法   // 該方法只要傳入一個字符串類名,程序可以根據該類名生成Java對象   private Object createObject(String clazzName) throws Exception {   // 根據字符串來獲取對應的Class對象   Class clazz = Class.forName(clazzName);   // 使用clazz對應類的默認構造器創建實例   return clazz.getConstructor().newInstance();   }     // 該方法根據指定文件來初始化對象池   // 它會根據配置文件來創建對象   public void initPool() throws Exception {   for (String name : config.stringPropertyNames()) {   // 每取出一個key-value對,如果key中不包含百分號(%)   // 這就表明是根據value來創建一個對象   // 調用createObject創建對象,並將對象添加到對象池中   if (!name.contains("%")) {   objectPool.put(name, createObject(config.getProperty(name)));   }   }   }     // 該方法將會根據屬性文件來調用指定對象的setter方法   public void initProperty() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {   for (String name : config.stringPropertyNames()) {   // 每取出一對key-value對,如果key中包含百分號(%)   // 即可認為該key用於控制調用對象的setter方法設置值   // %前半為對象名字,後半控制setter方法名   if (name.contains("%")) {   // 將配置文件中的key按%分割   String[] objAndProp = name.split("%");   // 取出調用setter方法的參數值   Object target = getObject(objAndProp[0]);   // 獲取setter方法名:set + "首字母大寫" + 剩下部分   String mtdName = "set" + objAndProp[1].substring(0, 1).toUpperCase() + objAndProp[1].substring(1);   // 通過target的getClass()獲取它的實現類所對應的Class對象   Class targetClass = target.getClass();   // 獲取希望調用的setter方法   Method mtd = targetClass.getMethod(mtdName, String.class);   // 通過Method的invoke方法執行setter方法   // 將config.getProperty(name)的值作為調用setter方法的參數   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 參數的 setter 方法,第二行粗體字代碼通過調用 Method 的 invoke() 方法來執行該 setter 方法,該方法執行完成後,就相當於執行了目標對象的 setter 方法。為上面程序提供如下配置文件。

a=javax.swing.JFrame
 b=javax.swing.JLabel
 #set the title of a
 a%title=Test Title

上面配置文件中的 a%title 行表明希望調用a對象的 setTitle() 方法,調用該方法的參數值為 Test Title。編譯、運行上面的 ExtendedObjectPoolFactory.java 程序,可以看到輸出一個 JFrame 窗口,該窗口的標題為 Test Title。

提示:Spring 框架就是通過這種方式將成員變量值以及依賴對象等都放在配置文件中進行管理的,從而實現了較好的解耦。這也是 Spring 框架的 IOC 的祕密。

當通過 Method 的 invoke() 方法來調用對應的方法時,Java 會要求程序必須有調用該方法的權限。如果程序確實需要調用某個對象的 private 方法,則可以先調用 Method 對象的如下方法。

  • setAccessible(boolean flag):將 Method 對象的 accessible 設置為指定的布爾值。值為true,指示該 Method 在使用時應該取消 Java 語言的訪問權限檢查:值為false,則指示該 Method 在使用時要實施 Java 語言的訪問權限檢查。

注意:實際上,setAccessible() 方法並不屬於 Method,而是屬於它的父類 AccessibleObject。因此 Method、Constructor、Field 都可調用該方法,從而實現通過反射來調用 private 方法、private 構造器和成員變量,下一節將會讓讀者看到這種示例。也就是說,它們可以通過調用該方法來取消訪問權限檢查,通過反射即可訪問 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 {   // 創建一個Person對象   Person p = new Person();   // 獲取Person類對應的Class對象   ClasspersonClazz = Person.class;   // 獲取Person的名為name的成員變量   // 使用getDeclaredField()方法表明可獲取各種訪問控制符的成員變量   Field nameField = personClazz.getDeclaredField("name");   // 設置通過反射訪問該成員變量時取消訪問權限檢查   nameField.setAccessible(true);   // 調用set()方法為p對象的name成員變量設置值   nameField.set(p, "Yeeku.H.Lee");   // 獲取Person類名為age的成員變量   Field ageField = personClazz.getDeclaredField("age");   // 設置通過反射訪問該成員變量時取消訪問權限檢查   ageField.setAccessible(true);   // 調用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() 方法則可以獲取所有的成員變量;第二行粗體字代碼則通過反射訪問該成員變量時不受訪問權限的控制;第三行粗體字代碼修改了 Person 對象的 name 成員變量的值。修改 Person 對象的 age 成員變量的值的方式與此完全相同。

編譯、運行上面程序,會看到如下輸出:

Person[name:Yeeku.H.Lee , age:30 ]

操作數組
 

在 java.lang.reflect 包下還提供了一個 Array 類,Array 對象可以代表所有的數組。程序可以通過使用 Array 來動態地創建數組,操作數組元素等。

Array 提供瞭如下幾類方法。

  • static Object newInstance(Class

    componentType,int...length):創建一個具有指定的元素類型、指定維度的新數組。
  • static xxx getXxx(Object array, int index):返回 array 數組中第 index 個元素。其中是各種基本數據類型,如果數組元素是引用類型,則該方法變為 get(Object array, int index)。

  • static void setXxx(Object array, int index, xxx val):將 array 數組中第 index 個元素的值設為 val。其中 xxx 是各種基本數據類型,如果數組元素是引用類型,則該方法變成 set(Object array, int index, Object val)。

下面程序示範瞭如何使用 Array 來生成數組,為指定數組元素賦值,並獲取指定數組元素的方式。

  public class ArrayTest1 {   public static void main(String args[]) {   try {   // 創建一個元素類型為String ,長度為10的數組   Object arr = Array.newInstance(String.class, 10);   // 依次為arr數組中index為5、6的元素賦值   Array.set(arr, 5, "瘋狂Java講義");   Array.set(arr, 6, "輕量級Java EE企業應用實戰");   // 依次取出arr數組中index為5、6的元素的值   Object book1 = Array.get(arr, 5);   Object book2 = Array.get(arr, 6);   // 輸出arr數組中index為5、6的元素   System.out.println(book1);   System.out.println(book2);   } catch (Throwable e) {   System.err.println(e);   }   }  }

 

上面程序中三行粗體字代碼分別是通過 Array 創建數組,為數組元素設置值,訪問數組元素的值的示例代碼,程序通過使用 Array 就可以動態地創建並操作數組。

下面程序比上面程序稍微複雜一點,下面程序使用 Array 類創建了一個三維數組。

  public class ArrayTest2 {   public static void main(String args[]) {   /*   * 創建一個三維數組。 根據前面介紹數組時講的:三維數組也是一維數組, 是數組元素是二維數組的一維數組,   * 因此可以認為arr是長度為3的一維數組   */   Object arr = Array.newInstance(String.class, 3, 4, 10);   // 獲取arr數組中index為2的元素,該元素應該是二維數組   Object arrObj = Array.get(arr, 2);   // 使用Array為二維數組的數組元素賦值。二維數組的數組元素是一維數組,   // 所以傳入Array的set()方法的第三個參數是一維數組。   Array.set(arrObj, 2, new String[] { "瘋狂Java講義", "輕量級Java EE企業應用實戰" });   // 獲取arrObj數組中index為3的元素,該元素應該是一維數組。   Object anArr = Array.get(arrObj, 3);   Array.set(anArr, 8, "瘋狂Android講義");   // 將arr強制類型轉換為三維數組   String[][][] cast = (String[][][]) arr;   // 獲取cast三維數組中指定元素的值   System.out.println(cast[2][3][8]);   System.out.println(cast[2][2][0]);   System.out.println(cast[2][2][1]);   }  }

 

上面程序的第一行粗體字代碼使用 Array 創建了一個三維數組,程序中較難理解的地方是第二段粗體字代碼部分,使用 Array 為 arrObj 的指定元素賦值,相當於為二維數組的元素賦值。由於二維數組的元素是一維數組,所以程序傳入的參數是一個一維數組對象。

運行上面程序,將看到 cast[2][3][8]、cast[2][2][0]、cast[2][2][1] 元素都有值,這些值就是剛才程序通過反射傳入的數組元素值。

 


[sl_ivan ] java使用反射創建並操作對象的方法已經有280次圍觀

http://coctec.com/docs/java/show-post-239373.html