Java 基礎篇之反射
- 2019 年 10 月 7 日
- 筆記
反射
使用反射獲取程式運行時的對象和類的真實資訊。
獲取 Class 對象
每個類被載入之後,系統會為該類生成一個對應的 Class 對象,通過該 Class 對象可以訪問到 JVM 中的這個類。
-
使用 Class 類的 forName(String clazzName) 靜態方法。字元串參數的值是某個類的全限定類名,必須包含完整的包名
-
調用某個類的 class 屬性
-
調用某個對象的 getClass() 方法。該方法是 java.lang.Object 類中的一個方法,所有的 Java 對象都可以調用,返回該對象所屬類對應的 Class 對象
獲取 Class 對象中資訊
Class 類提供了大量的實例方法來獲取該 class 對象所對應的類的詳細資訊。更多請參考 API。
import java.lang.reflect.*; import java.lang.annotation.*; public class ClassTest { private ClassTest() { } public ClassTest(String name) { System.out.println("執行有參數的構造器"); } public void info() { System.out.println("執行無參數的info方法"); } public void info(String str) { System.out.println("執行有參數的info方法" + ",其 str 參數值: " + str); } class Inner { } public static void main(String[] args) throws Exception { Class<ClassTest> clazz = ClassTest.class; // 獲取 clazz 對象所對應類的全部構造器 Constructor<?>[] ctros = clazz.getDeclaredConstructors(); System.out.println("ClassTest 的全部構造器如下: "); for (Constructor c : ctros) { System.out.println(c); } // 獲取 clazz 對象所對應類的全部 public 構造器 Constructor<?>[] publicCtors = clazz.getConstructors(); System.out.println("ClassTest的全部public構造器如下:"); for (Constructor c : publicCtors) { System.out.println(c); } // 獲取 clazz 對象所對應類的全部 public 方法 Method[] mtds = clazz.getMethods(); System.out.println("ClassTest 的全部 public 方法如下: "); for (Method md : mtds) { System.out.println(md); } // 獲取 clazz 對象所對應類的指定方法 System.out.println("ClassTest 裡帶一個字元串參數的 info 方法為:" + clazz.getMethod("info", String.class)); // 獲取 clazz 對象所對應類的全部註解 Annotation[] anns = clazz.getAnnotations(); System.out.println("ClassTest 的全部 Annotation 如下: "); for (Annotation an : anns) { System.out.println(an); } // 獲取 clazz 對象所對應類的全部內部類 Class<?>[] inners = clazz.getDeclaredClasses(); System.out.println("ClassTest 的全部內部類如下: "); for (Class c : inners) { System.out.println(c); } // 使用 Class.forName() 方法載入 ClassTest 的 Inner 內部類 Class inClazz = Class.forName("ClassTest$Inner"); // 訪問該類所在的外部類 System.out.println("inClazz 對應類的外部類為: " + inClazz.getDeclaringClass()); System.out.println("ClassTest 的包為:" + clazz.getPackage()); System.out.println("ClassTest 的父類為:" + clazz.getSuperclass()); } }
應用
Class 對象可以獲得對應類的方法(由 Method 表示)、構造器(由 Constructor 表示)、成員變數(由 Field 對象表示),且這個三個類都實現了 java.lang.reflect.Member 介面。程式可以通過 Method 對象來執行對應的方法,通過 Constructor 對象來調用對應的構造器創建實例,通過 Field 對象直接訪問並修改對象的成員變數值。
創建對象
-
使用 Class 對象的 newInstance() 方法來創建 Class 對象對應類的實例。要求該 Class 對象的對應類有默認構造器
-
先使用 Class 對象獲取指定的 Constructor 對象,再調用 Constructor 對象的 newInstance() 方法來創建該 Class 對象對應類的實例。這種方式可以選擇使用指定的構造器來創建實例
方式一
實現了一個簡單的對象池,該對象池會根據配置文件讀取 key-value 對,然後創建這些對象並放入 HashMap 中
import java.io.FileInputStream; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Properties; public class ObjectPoolFactory { private Map<String, Object> objectPool = new HashMap<>(); private Object createObject(String clazzName) throws InstantiationException, IllegalAccessException, ClassNotFoundException { Class<?> clazz = Class.forName(clazzName); // 使用 Class 對象對應的類的默認構造器 return clazz.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()) { objectPool.put(name, createObject(props.getProperty(name))); } } catch (IOException ex) { System.out.println("讀取" + fileName + "異常"); } } public Object getObject(String 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")); } } /* obj.txt 內容: a=java.util.Date b=javax.swing.JFrame */
方式二
import java.lang.reflect.Constructor; public class CreateJFrame { public static void main(String[] args) throws Exception { Class<?> jframeClazz = Class.forName("javax.swing.JFrame"); // 選擇使用指定的構造器 Constructor ctor = jframeClazz.getConstructor(String.class); Object obj = ctor.newInstance("測試窗口"); System.out.println(obj); } }
調用方法
每個 Method 對象對應一個方法,獲得 Method 對象後,就可以通過該 Method 來調用它對應的方法。
Method 包含一個 invoke() 方法,該方法的簽名如下:
- Object invoke(Object obj, Object… args):該方法中的 obj 是執行方法的主調(即類的實例對象),後面的 args 是執行該方法的實參
下面是對之前的對象工廠池進行增強,允許在配置文件中增加配置對象的成員變數值,對象池工廠會讀取該對象配置的成員變數值,並利用該對象對應的 setter 方法設置成員變數的值:
import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.Properties; 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 + "異常"); } } // 定義創建對象的方法 private Object createObject(String clazzName) throws InstantiationException, IllegalAccessException, ClassNotFoundException { // 根據字元串來獲取對應的 Class 對象 Class<?> clazz = Class.forName(clazzName); // 使用 clazz 對應類的默認構造器創建實例 return clazz.newInstance(); } // 初始化對象池 public void initPool() throws InstantiationException, IllegalAccessException, ClassNotFoundException { for (String name : config.stringPropertyNames()) { if (!name.contains("%")) { objectPool.put(name, createObject(config.getProperty(name))); } } } // 根據屬性文件來調用指定對象的 setter 方法 public void initProperty() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { for (String name : config.stringPropertyNames()) { if (name.contains("%")) { String[] objAndProp = name.split("%"); Object target = getObject(objAndProp[0]); String mtdName = "set" + objAndProp[1].substring(1); // 通過 target 的 getClass() 獲取它的實現類所對應的 Class 對象 Class<?> targetClass = target.getClass(); // 獲取希望調用的 setter 方法 Method mtd = targetClass.getMethod(mtdName, String.class); // 通過 Method 的 invoke 方法執行 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")); } } /* extObj.txt 內容 a=java.util.Date b=javax.swing.JFrame # set the title of a a%title=Test Title */
PS:當通過 Method 的 invoke() 方法來調用對應的方法時,Java 會要求程式必須有調用該方法的許可權。如果需要調用某個對象的 private 方法,則可以先調用 Method 對象的如下方法:
- setAccessible(boolean flag):值為 true,表示該 Method 在使用時取消訪問許可權檢查
訪問成員變數值
Filed 提供如下兩組方法來讀取或設置成員變數值:
-
getXxx(Object obj):獲取 obj 對象的該成員變數的值。此處的 Xxx 對應 8 中基本類型。如果成員變數的類型是引用類型,則直接使用 get
-
setXxx(Object obj, Xxx val):將 obj 對象的成員變數值設為 val 值。此處的 Xxx 對應 8 中基本類型。如果成員變數的類型是引用類型,則直接使用 set
public class Person { private String name; private int age; public String toString() { return "Person[name:" + name + ", age:" + age + "]"; } } import java.lang.reflect.Field; public class FieldTest { public static void main(String[] args) throws Exception { Person p = new Person(); Class<Person> personClazz = Person.class; Field nameField = personClazz.getDeclaredField("name"); nameField.setAccessible(true); nameField.set(p, "crazy"); Field ageField = personClazz.getDeclaredField("age"); ageField.setAccessible(true); ageField.setInt(p, 30); System.out.println(p); } }
泛型在反射中的應用
在反射中使用泛型,反射生成的對象就不需要進行強制類型轉換。
import java.util.Date; public class CrazyitObjectFactory { public static <T> T getinstance(Class<T> cls) { try { return cls.newInstance(); } catch (Exception e) { e.printStackTrace(); return null; } } public static void main(String[] args) { // 獲取實例後無需進行類型轉換 Date d = CrazyitObjectFactory.getinstance(Date.class); } }
歡迎關注我的公眾號