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); } } 歡迎關注我的公眾號
