淺談java反射機制

目錄

什麼是反射

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

初探

對反射的最初接觸是學習jdbc時,加載數據庫驅動時會這樣寫:Class.forName("com.mysql.jdbc.Driver"),當時似懂非懂的也不知道是什麼意思,隨着本身的不斷學習,愈來愈感受反射的神奇,讓咱們一塊兒來揭開它的神祕面紗吧。mysql

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


能夠看出,涉及的類並很少,讓我一塊兒來看一下其中比較經常使用的類的用法吧github

初始化

測試用例採用junit+log4j,新建一個test類,一個javabeanspring

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

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對象api

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反射給咱們帶了靈活性的同時,極大的方便了咱們的編程,並且反射堪稱各大框架的基礎。如此看來,顯然利大於弊,你怎麼看?

相關文章
相關標籤/搜索