咱們知道一個對象在運行時有兩種類型,一個是編譯類型,一個是運行時類型。在程序運行時,每每是須要發現類和對象的真實的信息的。那麼如何獲的這種信息呢?java
其一,若是咱們在編譯和運行時都知道類型的具體信息,這時是能夠手動將一個對象轉換爲運行時的類型。數組
其二,若是咱們在編譯時沒法預知對象和類究竟是屬於哪些類,那麼程序只有依靠運行時的信息來發現對象和類的真實的信息了,這時就必需要用到反射技術。安全
在談具體的反射技術以前,我想先回顧下,有關類的加載的一些基本的性質和原理,以方便咱們更好地理解,反射的做用和特色。而實際上,一個類若是能夠被執行,那麼對於JVM來講,它的執行流程爲:類的加載、鏈接、初始化。經過這種方式,才能夠獲取到一個類的類對象,即java.lang.Class對象,並在此基礎上獲取到該類的成員變量,方法和構造器等內在的東東。ide
那麼,什麼是類的加載呢?類的加載就是將類的class文件讀入內存,併爲之建立一個java.lang.Class對象,也就是說當程序使用任何類時,系統都會爲之創建一個java.lang.Class對象。函數
同時,這個java.lang.Class對象又有什麼特色呢?性能
一、Class是一個類,一個描述類的類(也就是描述類自己),封裝了描述方法的Method,描述字段的Filed,描述構造器的Constructor等屬性
二、對象照鏡子後(反射)能夠獲得的信息:某個類的數據成員名、方法和構造器、某個類到底實現了哪些接口。
三、對於每一個類而言,JRE 都爲其保留一個不變的 Class 類型的對象。一個 Class 對象包含了特定某個類的有關信息。
四、Class 對象只能由系統創建對象
五、一個類在 JVM 中只會有一個Class實例
同時,咱們瞭解下一個基本的類加載器的結構:測試
類的鏈接:鏈接階段負責把類的二進制數據合併到JRE中,分爲三個步驟:this
第一,驗證,檢驗被加載的類是否有正確的內部結構;第二,準備,負責爲類的類變量分配內存,並設置默認初始值。第三,解析將類的二進制數據中的符號引用替換成直接引用。編碼
類的初始化:主要對類變量進行初始化。spa
這樣,咱們清楚地認識到了一個類的生命週期變化,那麼這些也爲咱們的反射機制帶來了鋪墊,經過反射要獲取一個類的class對象,有三種方式:
(1)使用Class類的forName(String clazzName)靜態方法
(2)調用某個類的class屬性獲取該類對應的Class對象
(3)調用某個類的getClass()方法
經過這三種手段就能夠獲取到了一個類的Class對象,這樣就能夠動態獲取該對象的實際的信息了。下面經過一個具體的例子說明下,若是經過反射機制,建立一個類的對象,如何經過獲取的對象再進一步獲取該對象的屬性和方法,以及動態爲該對象注入新的參數值。
測試實體類:
package reflection; /** * @author jiaqing.xu@hand-china.com * @version 1.0 * @name * @description * @date 2017/10/19 */ public class Person { public String name; private int age; public Person(){ System.out.println("無參構造器"); } public Person(String name,int age){ this.name = name; this.age = age; } public String getName() { return name; } public 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 "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } private void privateMethod(){ System.out.println("這是一個私有的方法!"); } public void setName(String name,int age){ System.out.println("getName is:"+name); System.out.println("getAge is:"+age); } }
經過反射獲取類:
package reflection; /** * @author jiaqing.xu@hand-china.com * @version 1.0 * @name * @description 經過反射獲取類測試 * @date 2017/10/19 */ public class GetClassTest { public static void main(String[] args) throws ClassNotFoundException { //Class是一個描述類的類、對每一個類而言只有一個不變的Class與其對應、JRE中只有惟一一個 Class clazz = null; //1 直接經過類名.class方式獲得 clazz = Person.class; //輸出:reflection.Person System.out.println("直接經過類名.class獲得:"+clazz); //2 經過對象的getClass()方法獲取 Object obj = new Person(); clazz = obj.getClass(); //輸出:reflection.Person System.out.println("經過getClass(): " + clazz); //3 經過全類名獲取,用的比較多,但可能拋出ClassNotFoundException異常 clazz = Class.forName("reflection.Person"); //輸出:reflection.Person System.out.println("經過全類名獲取: " + clazz); } }
獲取類的實例:
package reflection; /** * @author jiaqing.xu@hand-china.com * @version 1.0 * @name * @description 經過反射獲取類的實例測試 * @date 2017/10/19 */ public class GetInstanceTest { public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException { Class clazz = Class.forName("reflection.Person"); //使用Class類的newInstance()方法建立類的一個對象 //實際調用的類的那個 無參數的構造器 //通常的,一個類若聲明瞭帶參數的構造器,也要聲明一個無參數的構造器 Object obj = clazz.newInstance(); System.out.println(obj.toString()); } }
獲取類所定義的方法:
package reflection; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * @author jiaqing.xu@hand-china.com * @version 1.0 * @name * @description 經過反射獲取類所定義的方法的測試 * @date 2017/10/19 */ public class GetMethodTest { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException { //獲取clazz Class clazz = Class.forName("reflection.Person"); //一、獲得clazz 對應的類中有哪些方法,不能獲取private方法 Method[] methods = clazz.getMethods(); System.out.println("getMethods:");
//遍歷方法集合 獲取對應的方法名稱 for (Method method : methods) { System.out.print(method.getName() + ", "); } System.out.println(); //二、獲取全部的方法(包括private方法) Method[] methods2 = clazz.getDeclaredMethods(); System.out.println("getDeclaredMethods:"); for (Method method : methods2) { System.out.print(method.getName() + ", "); } System.out.println(); //三、獲取指定的方法 Method method = clazz.getDeclaredMethod("setName", String.class); System.out.println("method : " + method); Method method2 = clazz.getDeclaredMethod("setName", String.class, int.class);//第一個參數是方法名,後面的是方法裏的參數 System.out.println("method2: " + method2); //四、執行方法 Object obj = clazz.newInstance();
//此處注意下該invoke方法,參數爲:執行該方法的對象,爲該方法傳入的參數列表 method2.invoke(obj, "jaycee", 23); } }
獲取Fileds:
package reflection; import java.lang.reflect.Field; /** * @author jiaqing.xu@hand-china.com * @version 1.0 * @name * @description 經過反射獲取field測試 * @date 2017/10/19 */ public class GetFieldTest { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { Class clazz = Class.forName("reflection.Person"); //一、獲取字段 //1.1 獲取Field的數組,私有字段也能獲取 Field[] fields = clazz.getDeclaredFields(); for (Field field: fields) { System.out.print(field.getName() + ", "); } //1.2 獲取指定名字的Field Field field = clazz.getDeclaredField("name"); System.out.println("\n獲取指定Field名=: " + field.getName()); Person person = new Person("jaycee", 23); //二、獲取指定對象的Field的值 Object val = field.get(person); System.out.println("獲取指定對象字段'name'的Field的值=:" + val); //三、設置指定對象的Field的值 field.set(person, "admin"); System.out.println("設置指定對象字段'name'的Field的值=: " + person.name); //四、若該字段是私有的,須要調用setAccessible(true)方法 Field field2 = clazz.getDeclaredField("age"); field2.setAccessible(true); System.out.println("獲取指定私有字段名=: " + field2.getName()); } }
獲取父類定義的信息:
申明父類:
package reflection.invoke; /** * @author jiaqing.xu@hand-china.com * @version 1.0 * @name * @description 父類 * @date 2017/10/19 */ public class Father { /** * 父類無參構造函數 */ public Father(){ } /** * 父類私有方法1 */ private String method(){ return "Father's private method"; } }
申明子類:
package reflection.invoke; /** * @author jiaqing.xu@hand-china.com * @version 1.0 * @name * @description 兒子類 * @date 2017/10/19 */ public class Son extends Father{ /** * @author jiaqing.xu@hand-china.com * @date 2017/10/19 10:19 * @param * @return * @description 兒子私有方法 */ private void method1(Integer age){ System.out.println("son's age=:" +age); } }
獲取父類中定義的方法:
package reflection.invoke; /** * @author jiaqing.xu@hand-china.com * @version 1.0 * @name * @description 獲取父類中定義的私有的方法 * @date 2017/10/19 */ public class GetSuperClassTest { public static void main(String[] args) throws ClassNotFoundException { String className = "reflection.invoke.Son"; Class clazz = Class.forName(className); //獲得父類的Class Class superClazz = clazz.getSuperclass(); //輸出:class reflection.invoke.Father System.out.println(superClazz); } }
總結下反射的優勢和缺點:
1 優勢: 2 (1)提升程序的靈活性和拓展性,可以在運行時動態地獲取類的實例。 3 (2)和java的動態編譯相結合,能夠提升更強大的功能。 4 (3)提早無需硬編碼,即可以經過類名獲取對應類的實例,進而操做該實例。 5 6 缺點: 7 (1)性能較低,反射是一種接入式的操做須要找到對應的字段和方法,比起直接的代碼賦值,要慢的多。 8 (2)使用反射應該在一個相對安全的環境下進行。 9 (3)使用時會破壞類的封裝性,破壞OOP的總體設計思想。