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