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);      }  }

歡迎關注我的公眾號