淺談java反射機制


目錄

什么是反射

JAVA反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意方法和屬性;這種動態獲取信息以及動態調用對象方法的功能稱為java語言的反射機制。

初探

對反射的最初接觸是學習jdbc時,加載數據庫驅動時會這樣寫:Class.forName("com.mysql.jdbc.Driver"),當時似懂非懂的也不知道是什么意思,隨着自己的不斷學習,越來越感覺反射的神奇,讓我們一起來揭開它的神秘面紗吧。

學習一個知識,自然是最先從api開始,反射涉及的類,除了Class類之外,基本上都在java.lang.reflect包里面,常用的類有Constructor,Field,Method類等,AccessibleObject類是前面三個類的基類,主要包含設置安全性檢查等方法,下面,我們看一下reflect包的結構

可以看出,涉及的類並不多,讓我一起來看一下其中比較常用的類的用法吧 ## 初始化 測試用例采用junit+log4j,新建一個test類,一個javabean

其中name屬性get,set方法用private修飾

User類

package com.test;

public class User {
  private String name = "init";
  private int age;
  public User() {}
  public User(String name, int age) {
    super();
    this.name = name;
    this.age = age;
  }
  private String getName() {
    return name;
  }
  private void setName(String name) {
    this.name = name;
  }
  public int getAge() {
    return age;
  }
  public void setAge(int age) {
    this.age = age;
  }
  @Override
  public String toString() {
    return "User [name=" + name + ", age=" + age + "]";
  }
}

Test類

public class ReflectTest {
  private static Logger logger = Logger.getLogger(ReflectTest.class);
  private static Class<User> userClass = User.class;
}

在類加載的時候,jvm會創建一個class對象

class對象是可以說是反射中最常用的,獲取class對象的方式的主要有三種

  1. 根據類名:類名.class
  2. 根據對象:對象.getClass()
  3. 根據全限定類名:Class.forName(全限定類名)
  @Test
  public void classTest() throws Exception {
    // 獲取Class對象的三種方式
    logger.info("根據類名:  \t" + User.class);
    logger.info("根據對象:  \t" + new User().getClass());
    logger.info("根據全限定類名:\t" + Class.forName("com.test.User"));
    // 常用的方法
    logger.info("獲取全限定類名:\t" + userClass.getName());
    logger.info("獲取類名:\t" + userClass.getSimpleName());
    logger.info("實例化:\t" + userClass.newInstance());
  }

console

根據類名:  	class com.test.User
根據對象:  	class com.test.User
根據全限定類名:	class com.test.User
獲取全限定類名:	com.test.User
獲取類名:	com.test.User
實例化:	User [name=init, age=0]

構造函數

構造函數是java創建對象的必經之路,所以通過反射拿到一個類的構造函數后,再去創建這個類的對象自然是易如反掌,常用的方法如下:

  @Test
  public void constructorTest() throws Exception {
    // 獲取全部的構造函數
    Constructor<?>[] constructors = userClass.getConstructors();
    // 取消安全性檢查,設置后才可以使用private修飾的構造函數,也可以單獨對某個構造函數進行設置
    // Constructor.setAccessible(constructors, true);
    for (int i = 0; i < constructors.length; i++) {
      Class<?> parameterTypesClass[] = constructors[i].getParameterTypes();
      System.out.print("第" + i + "個構造函數:\t (");
      for (int j = 0; j < parameterTypesClass.length; j++) {
        System.out.print(parameterTypesClass[j].getName() + (j == parameterTypesClass.length - 1 ? "" : "\t"));
      }
      logger.info(")");
    }
    // 調用構造函數,實例化對象
    logger.info("實例化,調用無參構造:\t" + constructors[0].newInstance());
    logger.info("實例化,調用有參構造:\t" + constructors[1].newInstance("韋德", 35));
  }

console

第0個構造函數:	 ()
第1個構造函數:	 (java.lang.String	int)
實例化,調用無參構造:	User [name=init, age=0]
實例化,調用有參構造:	User [name=韋德, age=35]

屬性

猶記得學習spring ioc之時,對未提供set方法的private屬性依然可以注入感到神奇萬分,現在看來,這神奇的根源自然是來自於java的反射,常用的方法如下:

  @Test
  public void fieldTest() throws Exception {
    User user = userClass.newInstance();
    // 獲取當前類所有屬性
    Field fields[] = userClass.getDeclaredFields();
    // 獲取公有屬性(包括父類)
    // Field fields[] = cl.getFields();
    // 取消安全性檢查,設置后才可以獲取或者修改private修飾的屬性,也可以單獨對某個屬性進行設置
    Field.setAccessible(fields, true);
    for (Field field : fields) {
      // 獲取屬性名 屬性值 屬性類型
      logger.info("屬性名:" + field.getName() + "\t屬性值:" + field.get(user) + "  \t屬性類型:" + field.getType());
    }
    Field fieldUserName = userClass.getDeclaredField("name");
    // 取消安全性檢查,設置后才可以獲取或者修改private修飾的屬性,也可以批量對所有屬性進行設置
    fieldUserName.setAccessible(true);
    fieldUserName.set(user, "韋德");
    // 可以直接對 private 的屬性賦值
    logger.info("修改屬性后對象:\t" + user);
  }

console

屬性名:name	屬性值:init  	屬性類型:class java.lang.String
屬性名:age	屬性值:0  	屬性類型:int
修改屬性后對象:	User [name=韋德, age=0]

方法

大家對javabean肯定不會陌生,在用框架操作javabean時,大多都是通過反射調用get,set方法Javabean進行操作,常用的方法如下:

  @Test
  public void methodTest() throws Exception {
    User user = userClass.newInstance();
    // 獲取當前類的所有方法
    Method[] methods = userClass.getDeclaredMethods();
    // 獲取公有方法(包括父類)
    // Method[] methods = userClass.getMethods();
    // 取消安全性檢查,設置后才可以調用private修飾的方法,也可以單獨對某個方法進行設置
    Method.setAccessible(methods, true);
    for (Method method : methods) {
      // 獲取方法名和返回類型 獲取參數類型:getParameterTypes
      logger.info("方法名:" + method.getName() + " \t返回類型:" + method.getReturnType().getName());
    }
    // 獲取無參方法
    Method getMethod = userClass.getDeclaredMethod("getName");
    // 取消安全性檢查,設置后才可以調用private修飾的方法,也可以批量對所有方法進行設置
    getMethod.setAccessible(true);
    // 調用無參方法
    logger.info("調用getName方法:" + getMethod.invoke(user));
    // 獲取有參方法
    Method setMethod = userClass.getDeclaredMethod("setName", String.class);
    // 取消安全性檢查,設置后才可以調用private修飾的方法,也可以批量對所有方法進行設置
    setMethod.setAccessible(true);
    // 調用有參方法
    logger.info("調用setName方法:" + setMethod.invoke(user, "韋德"));
    logger.info("通過set方法修改屬性后對象:\t" + user);
  }

console

方法名:toString 	返回類型:java.lang.String
方法名:setAge 	返回類型:void
方法名:getAge 	返回類型:int
方法名:getName 	返回類型:java.lang.String
方法名:setName 	返回類型:void
調用getName方法:init
調用setName方法:null
通過set方法修改屬性后對象:	User [name=韋德, age=0]

完整的 源碼 https://github.com/zhaoguhong/blogsrc

總結

不難看出,Java反射中的構造函數,屬性,方法有着諸多相似之處,不僅僅是因為它們有着共同的父類AccessibleObject,基本上所有的api都有相似之處。學習的過程中死記api是最愚蠢的,找方法,理解反射的設計思路。去嘗試感悟設計思想,才是王道。

上面只是對反射的常用方法提供了示例,最好的學習方法自然是參照api,自己去實踐。紙上得來終覺淺,絕知此事要躬行。通過自己的不斷練習,體會,思考,達到融會貫通的目的。

思考

java以面向對象和封裝性著稱,但反射在java中堪稱作弊器,似乎無所不能,給人一種建了一道圍牆,下面又留了一道門的感覺,是否破壞了程序的封裝性?

筆者認為:循規蹈矩固然好,但過於注重規范反而影響程序的靈活性。Java反射給我們帶了靈活性的同時,極大的方便了我們的編程,而且反射堪稱各大框架的基礎。如此看來,顯然利大於弊,你怎么看?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM