前面介紹瞭如何利用反射技術讀寫私有屬性,不單是私有屬性,就連私有方法也能通過反射技術來調用。為了演示反射的逆天功能,首先給Chicken雞類增加下列幾個私有方法,簡單起見弄來了set***/get***這樣的基本方法: 參照私有屬性的反射操作過程,私有方法的反射調用可分解為如下三個步驟: 1、調用C ...
前面介紹瞭如何利用反射技術讀寫私有屬性,不單是私有屬性,就連私有方法也能通過反射技術來調用。為了演示反射的逆天功能,首先給Chicken雞類增加下列幾個私有方法,簡單起見弄來了set***/get***這樣的基本方法:
private void setName(String name) { // 設置名稱 this.name = name; } private String getName() { // 獲取名稱 return this.name; } private void setSex(int sex) { // 設置性別 this.sex = sex; } private int getSex() { // 獲取性別 return this.sex; }
參照私有屬性的反射操作過程,私有方法的反射調用可分解為如下三個步驟:
1、調用Class對象的getDeclaredMethod方法,獲取指定名稱的方法對象,即Method對象;
2、調用Method對象的setAccessible方法,並傳入true值,表示將該方法設置為允許訪問,以解除private的限制;
3、調用Method對象的invoke方法,並傳入雞類實例,酌情填寫輸入參數;
雖然方法只有調用一說,沒有讀寫之分,但是方法的輸入參數可以有也可以沒有,同樣輸出參數可以有也可以沒有,因而對於方法對象而言,反射技術需要支持以下四種情況:有輸入參數、無輸入參數、有輸出參數、無輸出參數。註意到Chicken類的新增方法getName無輸入參數、有輸出參數,setName有輸入參數、無輸出參數,故而只要實現getName與setName兩個方法的反射調用,剛好就覆蓋了有無入參和有無出參這四種場景。
先來看getName,因為該方法沒有輸入參數,所以反射調用相對簡單,只是invoke方法的返回值為Object類型,需要強制轉換成String類型,這樣才能獲得雞的名稱。此時獲取名稱的反射代碼如下所示:
// 通過反射來調用某個實例的私有方法(getName方法) private static String getReflectName(Chicken chicken) { String name = ""; try { Class cls = Chicken.class; // 獲得Chicken類的基因類型 // 通過方法名稱及參數列表獲取該方法的Method對象 Method method = cls.getDeclaredMethod("getName"); method.setAccessible(true); // 將該方法設置為允許訪問 name = (String) method.invoke(chicken); // 調用某實例的方法並獲得輸出參數 } catch (Exception e) { // 捕捉到了任何一種異常(錯誤除外) e.printStackTrace(); } return name; }
再來看setName,由於該方法存在輸入參數,因此調用Class對象的getDeclaredMethod之時,需要傳入參數類型列表。之所以這麼做,是因為同名方法可能會被多次重載,重載後的方法通過參數個數與參數類型加以區分。另外,invoke方法也要傳入setName方法所需的各項參數值。一系列調整之後,設置名稱的反射代碼改寫如下:
// 通過反射來調用某個實例的私有方法(setName方法) private static void setReflectName(Chicken chicken, String name) { try { Class cls = Chicken.class; // 獲得Chicken類的基因類型 // 通過方法名稱及參數列表獲取該方法的Method對象 // 之所以需要參數類型列表,是因為同名方法可能會被多次重載,重載後的方法通過參數個數與參數類型加以區分 Method method = cls.getDeclaredMethod("setName", String.class); method.setAccessible(true); // 將該方法設置為允許訪問 method.invoke(chicken, name); // 攜帶輸入參數調用某實例的方法 } catch (Exception e) { // 捕捉到了任何一種異常(錯誤除外) e.printStackTrace(); } }
編寫完成名稱獲取與名稱設置的反射代碼,獲取性別與設置性別的反射代碼即可如法炮製,區別主要有兩處,一處的強制類型轉換把“(int)”換成“(String)”,另一處的參數類型列表把“String.class”換成“int.class”。性別獲取與性別設置的反射代碼示例如下:
// 通過反射來調用某個實例的私有方法(getSex方法) private static int getReflectSex(Chicken chicken) { int sex = -1; try { Class cls = Chicken.class; // 獲得Chicken類的基因類型 // 通過方法名稱及參數列表獲取該方法的Method對象 Method method = cls.getDeclaredMethod("getSex"); method.setAccessible(true); // 將該方法設置為允許訪問 sex = (int) method.invoke(chicken); // 調用某實例的方法並獲得輸出參數 } catch (Exception e) { // 捕捉到了任何一種異常(錯誤除外) e.printStackTrace(); } return sex; } // 通過反射來調用某個實例的私有方法(setSex方法) private static void setReflectSex(Chicken chicken, int sex) { try { Class cls = Chicken.class; // 獲得Chicken類的基因類型 // 通過方法名稱及參數列表獲取該方法的Method對象 // 之所以需要參數類型列表,是因為同名方法可能會被多次重載,重載後的方法通過參數個數與參數類型加以區分 Method method = cls.getDeclaredMethod("setSex", int.class); method.setAccessible(true); // 將該方法設置為允許訪問 method.invoke(chicken, sex); // 攜帶輸入參數調用某實例的方法 } catch (Exception e) { // 捕捉到了任何一種異常(錯誤除外) e.printStackTrace(); } }
然後輪到外部調用這幾個封裝好的反射方法了,準備把公雞實例的名稱改為“母鴨”,性別改為“雌性”,具體的調用代碼如下所示:
Cock cock = new Cock(); // 創建一個公雞實例 System.out.println("準備修理公雞, 名稱 = "+getReflectName(cock)+", 性別 = "+getReflectSex(cock)); setReflectName(cock, "母鴨"); // 把公雞實例的名稱篡改為“母鴨” setReflectSex(cock, cock.FEMALE); // 把公雞實例的性別篡改為“雌性” System.out.println("結束修理公雞, 名稱 = "+getReflectName(cock)+", 性別 = "+getReflectSex(cock));
運行上面的演示代碼,觀察到下麵的日誌信息,可見一隻雄赳赳的公雞被硬生生整成了母鴨模樣:
準備修理公雞, 名稱 = 公雞, 性別 = 0 結束修理公雞, 名稱 = 母鴨, 性別 = 1
更多Java技術文章參見《Java開發筆記(序)章節目錄》