深入剖析JAVA的反射機制

定義

在Java 的運行時環境中,對于任意的一個類,都能夠知道這個類的所有屬性和方法,對于任意一個對象都能夠調用任意的方法和屬性,這種動態的獲取類的信息以及動態的調用對象方法的功能,稱之為反射(注意是運行狀態,非編譯)。

Java反射提供的主要功能:
  • 在運行時判斷任意一個對象所屬的類
  • 在運行時構造任意一個類的對象
  • 在運行時判斷任意一個類所具有的成員變量和方法
  • 在運行時調用任意一個對象的方法
Java反射機制實現類:
  • Field類:代表類的成員變量
  • Method類:代表類的方法
  • Constructor類:代表類的構造方法
  • Array類:提供動態的創建數組
    • 以上四個類都位于java.lang.reflect 包中
  • Class類:代表一個類 , 位于java.lang 包中
反射的使用

在JAVA中,無論一個類生成多少個對象,這些對象都會對應同一個Class對象 。要想使用反射,首先需要獲得待處理的類或者對象所對應的Class對象。該Class對象是在類被加載之后,由系統自動生成的。獲取該Class對象有三種方式:

  1. 使用Class類的靜態方法forName: Class.forName("java.lang.String") ,參數必須添加完整的路徑,包括包名。
  2. 使用類的.class 語法: String.class
  3. 使用對象的getClass()方法: String s = "reflect" ;Class<?> clazz = s.getClass()

以上便是獲取Class對象的三種方式,要使用反射,必須先獲取Class對象,一旦獲取了Class對象之后,就可以調用Class對象的方法來獲得對象和該類的真實信息。

反射相關API ,使用反射生成并操作對象

Class對象可以獲得帶操作類里的方法(Method),構造器(constructor),Field 。要操作方法需要獲得Mehtod對象,要操作構造器需要獲得Constructor對象,要操作屬性需要獲得Field對象。

下面將會結合具體的實例,來說明生成Class對象的三種方式是如何使用的:

反射初體驗,通過第一種方式,獲取Class對象,并打印java.lang.String的所有聲明方法:

Class<?> aClass = Class.forName("java.lang.String");  //獲取String類的Class對象
Method[] methdos = aClass.getDeclaredMethods(); //獲取所有的聲明方法
for(Method m : methdos)  //循環遍歷Method數組
   System.out.println(m);   //打印方法名稱,包含了private 方法

運行結果:

部分運行結果截圖

通過反射調用某個類的方法

public class InvokeTest {

    public int add(int a , int b){
        return a +b ;
    }
    public String echo(String message){
        return "Hello :" +message ;
    }

    public static void main(String[] args) throws Exception {
        //通過new的方式調用
        //InvokeTest invokeTest = new InvokeTest();
        //System.out.println(invokeTest.add(1 , 2));
        //System.out.println(invokeTest.echo("Tom"));

        //通過反射調用方法
        //通過內置語法,獲取Class對象
        Class<InvokeTest> invokeTestClass = InvokeTest.class;
        //創建Class對象表示的類的實例 ,調用Class對象的newInstance()方法
        InvokeTest invokeTest = invokeTestClass.newInstance();
        
        //獲取待操作類的Method對象。傳入方法名字,和方法的參數類型 
        Method add = invokeTestClass.getDeclaredMethod("add", new Class[]{int.class, int.class});
        //使用這種方式也是可以得,因為getDeclaredMethod()第二個參數是一個可變參數
        //Method add = InvokeTestClass.getDeclaredMethod("add" , int.class , int.class) 
        Method echo = invokeTestClass.getDeclaredMethod("echo", new Class[]{String.class});
        
        //調用Method對象的invoke方法,表示含義為,調用invokeTest對象的add/echo方法,并傳出參數值
        Object addResult = add.invoke(invokeTest, new Object[]{1, 2});
        Object echoResult = echo.invoke(invokeTest, new Object[]{"Tome"});
        
        //打印輸出的結果
        System.out.println((Integer) addResult); 
        System.out.println((String) echoResult);  
        
        //運行結果
        //3
        //Hello :Tome
    }
}

通過反射調用某個類的構造方法 ,創建對象

public class ConstructorTest {
    public static void main(String[] args) throws Exception {
        User user = new User() ;
        //使用第三種獲取Class對象的方式。使用getClass()方法
        Class<?> userClass = user.getClass();
        //調用無參構造方法,創建對象實例,反射無法得知返回的類型,需要強制類型轉換
        Constructor<?> constructor = userClass.getConstructor();
        User user1 = (User)constructor.newInstance();
        //上面兩行代碼可以替換為下面一行代碼
        //User u = (User)userClass.newInstance();
        user1.setAge(18);
        user1.setName("zhangsan");
        System.out.println(user1.getName() +"," + user1.getAge()); //zhangsan,18
        System.out.println("--------------------------");
        //調用有參構造方法,下面兩行方法中的參數要一一對應
        Constructor<?> constructor1 = userClass.getConstructor(new Class[]{String.class, int.class});
        User user2 = (User)constructor1.newInstance(new Object[]{"lisi", 30});
        System.out.println(user2.getName() +"," +user2.getAge());
    }
}

class User{
    private String name ;
    private int age ;

    public User(){}

    public User(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;
    }
}

通過反射生成對象有兩種方式:

1.使用Class對象的newInstance()方法,這種方法要求對象的對應類有無參構造方法
2.先使用Class對象獲得Constructor()對象,在調用Constructor對象的newInstance()方法。這種方式可以調用無參的構造方法,也可以調用有參的構造方法。但是Constructor()和newInstance()中的參數要一一對應。可參考上例。

通過反射操作類的屬性

public class FieldTest {
    public static void main(String[] args) throws  Exception {
        //獲取Class對象,并生成實例對象
        Class<?> personClass = Person.class;
        Object p = personClass.newInstance();
        //通過Class對象的getDeclaredField()方法,獲取類的field
        Field name = personClass.getDeclaredField("name");
        Field age = personClass.getDeclaredField("age");
        //name字段為private 修飾,使用setAccessible()方法,取消訪問權限檢查
        name.setAccessible(true);
        //調用set()方法,設置p對象的值
        name.set(p , "zhangsan");
        age.set(p , 18);
        //打印值
        System.out.println(p); //name: zhangsan, age: 18 
    }
}

class Person{
    private String name ;
    public  int age ;

    @Override
    public String toString() {
        return  "name: "+name +", age: "+age ;
    }
}

通過setAccessible()方法,可以通過反射訪問類中的私有屬性和私有方法,通常不建議這樣使用,因為它打破了封裝的特性。但是在一些特定情況中,還是需要使用這種方式的。

通過反射操作數組

public class ArrayTest {

    public static void main(String[] args) {
        //創建一個類型為String, 長度為10 的數組 
        Object o = Array.newInstance(String.class, 10);
        
        //為數組賦值
        Array.set(o , 2 ,"hello");
        Array.set(o , 5 ,"world");
        
        //獲取數組的值,并打印
        System.out.println(Array.get(o , 2));  //hello
        System.out.println(Array.get(o , 5)); //world
    }
}

上面簡單介紹了幾個常用的方法,旨在說明反射對方法,構造方法,字段,數組中的調用方式,除了上面的這些,還可以獲取父類,接口,包 ,全部的方法聲明,全部的field聲明等與類相關的全部信息。想要了解更多內容,請自行查閱API。

通過反射生成動態代理

先看代碼,稍后再解釋動態代理的實現過程,這個地方可能有點難理解,多看幾遍就可以了。

/*定義接口*/
public interface Subject {
    public void sayHello(String name);
}

/*定義接口的實現類,也就是真實的代理調用對象*/
public class RealSubject implements Subject{
    public void sayHello(String name ) {
        System.out.println("hello : " + name );
    }
}

/*定義動態代理類*/
public class DynamicProxy implements InvocationHandler{

    private Object obj = null ; //此處定義了Object類型,表示可以代理任何對象
    public DynamicProxy(Object obj){
        this.obj = obj ;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        //調用obj對象的method方法,并傳入參數args,本例中相當于調用obj對象的sayHello()方法,并傳入Tom 參數
        method.invoke(obj , args) ;
        after();
        return null ; //Subject接口中方法沒有返回值,返回Null 
    }

    //此方法表示代理的額外操作。
    private void before(){
        System.out.println("before proxy");
    }
    //此方法表示代理的額外操作
    private void after(){
        System.out.println("after proxy");
    }
}

客戶端調用:

public class Client {
    public static void main(String[] args) {

        //需要代理的真實對象
        RealSubject realSubject = new RealSubject() ;
        //創建代理類,并將真實的代理對象,傳入構造方法中
        InvocationHandler handler = new DynamicProxy(realSubject);

        //運行時動態生成的代理實例,實現了RelSubject類所實現的接口,所以可以強制轉換為Subject類型,第一個參數是類加載器,第二個參數是代理類實現的接口,第三個參數是處理實際工作的handler實例
        Subject subject = (Subject)Proxy.newProxyInstance(realSubject.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), handler);
        //調用接口中聲明的方法 ,流程跳轉到handler的invoke()方法中執行。
        subject.sayHello("Tom");
    }
}

總結:

Java的動態代理類位于java.lang.reflect包下,涉及到兩個類:
interface InvocationHandler: 該接口中只定義了一個方法-invoke(),使用動態代理類時,必須實現這個接口。
Porxy:該類即為動態代理類,常用newProxyInstance()來生成代理實例,它不會做什么實質性的工作,實質性的工作是由與之關聯的InvocationHandle來處理的。也就是說生成的代理對象都有一個與之關聯的InvocationHandler對象。

動態代理的使用步驟:

  • 創建被代理類的接口和實現類,因為動態代理是基于接口的。這也是動態代理的一個小小缺憾。
  • 創建實現InvocationHandler接口的類,并實現invoke()方法
  • 通過Proxy的靜態方法newProxyInstance(ClassLoader loader ,Class[] interfaces ,InvocationHandler handler) 創建一個代理實例。
  • 通過上面創建的代理實例,調用方法

至此,End!



少年聽雨歌樓上,紅燭昏羅帳。  
壯年聽雨客舟中,江闊云低,斷雁叫西風。
感謝支持!
                                        ---起個名忒難

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,908評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,324評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,018評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,675評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,417評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,783評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,779評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,960評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,522評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,267評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,471評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,009評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,698評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,099評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,386評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,204評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,436評論 2 378

推薦閱讀更多精彩內容

  • 一、概述 Java反射機制定義 Java反射機制是在運行狀態中,對于任意一個類,都能夠知道這個類中的所有屬性和方法...
    CoderZS閱讀 1,648評論 0 26
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,733評論 18 399
  • 一、概述 1、Java反射機制(Java-Reflect): 在運行狀態中,對于任意一個類,都能夠知道這個類中的所...
    年少懵懂丶流年夢閱讀 4,454評論 0 5
  • 帶節點進度條的實現方法不止一個,但是如果要實現圖中這種效果的,初步看好像還不簡單。進度條的形狀不規則、背景是漸變顏...
    NanBox閱讀 4,671評論 3 35
  • 事實證明,二年級玩奧數是可以的,請看下圖: 為什么孩子會如此興奮,因為我們這里是俱樂部,不是集訓營! 在這里老師像...
    何立泵閱讀 265評論 0 2