深入淺出反射

什么是反射?

反射是一種能夠在程序運行時動態訪問、修改某個類中任意屬性(狀態)和方法(行為)的機制(包括private實例和方法),java反射機制提供了以下幾個功能:

  • 在運行時判斷任意一個對象所屬的類;

  • 在運行時構造任意一個類的對象;

  • 在運行時判斷任意一個類所具有的成員變量和方法;

  • 在運行時調用任意一個對象的方法。

反射涉及到四個核心類:

  • java.lang.Class.java:類對象;

  • java.lang.reflect.Constructor.java:類的構造器對象;

  • java.lang.reflect.Method.java:類的方法對象;

  • java.lang.reflect.Field.java:類的屬性對象;

反射有什么用?

  • 操作因訪問權限限制的屬性和方法;

  • 實現自定義注解;

  • 動態加載第三方jar包,解決android開發中方法數不能超過65536個的問題;

  • 按需加載類,節省編譯和初始化APK的時間;

反射工作原理

當我們編寫完一個Java項目之后,每個java文件都會被編譯成一個.class文件,這些Class對象承載了這個類的所有信息,包括父類、接口、構造函數、方法、屬性等,這些class文件在程序運行時會被ClassLoader加載到虛擬機中。當一個類被加載以后,Java虛擬機就會在內存中自動產生一個Class對象。我們通過new的形式創建對象實際上就是通過這些Class來創建,只是這個過程對于我們是不透明的而已。

反射的工作原理就是借助Class.java、Constructor.java、Method.java、Field.java這四個類在程序運行時動態訪問和修改任何類的行為和狀態。

反射實例

分別演示三種獲取類信息的方式、獲取當前類的所有方法和獲取當前類及其父類的所有方法、獲取當前類的所有實例和獲取當前類及其父類的所有實例、獲取父類信息、獲取接口信息、比較反射方法和實例的性能差異等幾個方面:

  • 示例類:

父類Personon.java:

package com.eebbk.reflectdemo;

public class Person{
    String mName;
    String mSex;
    public int mAge;

    public Person(String aName, String aSex, int aAge) {
        mName = aName;
        mSex = aSex;
        mAge = aAge;
    }

    public int getmAge(){
        return mAge;
    }

    public void setmAge(int mAge){
        this.mAge = mAge;
    }

    public String getmName(){
        return mName;
    }

    public void setmName(String mName){
        this.mName = mName;
    }

    public String getmSex(){
        return mSex;
    }

    public void setmSex(String mSex){
        this.mSex = mSex;
    }

    private String getDescription(){
        return "黃種人";
    }
}

接口ICompany.java:

package com.eebbk.reflectdemo;

public interface ICompany{
    String getCompany();
}

子類ProgramMonkey.java:

package com.eebbk.reflectdemo;

public class ProgramMonkey extends Person implements ICompany{
    String mLanguage = "C#";
    String mCompany = "BBK";

    public ProgramMonkey(String aName, String aSex, int aAge){
        super(aName, aSex, aAge);
    }

    public ProgramMonkey(String language, String company, String aName, String aSex, int aAge){
        super(aName, aSex, aAge);
        mLanguage = language;
        mCompany = company;
    }

    public String getmLanguage(){
        return mLanguage;
    }

    public void setmLanguage(String mLanguage){
        this.mLanguage = mLanguage;
    }

    private int getSalaryPerMonth(){
        return 12306;
    }

    @Override
    public String getCompany(){
        return mCompany;
    }
}

示例類ReflectActivity.java:

public class ReflectActivity extends Activity{
    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_reflect_layout);
    }

    public void onClick(View v){
        switch(v.getId()){
            case R.id.getClassObjectBtnId:{
                getClassObject();
            }
            break;
            case R.id.getMethodInfoBtnId:{
                getMethodInfo();
            }
            break;
            case R.id.getFieldInfoBtnId:{
                getFieldInfo();
            }
            break;
            case R.id.getSuperClassInfoBtnId:{
                getSuperClass();
            }
            break;
            case R.id.getInterfaceInfoBtnId:{
                getInterfaces();
            }
            break;
            case R.id.compareMethodAndFieldBtnId:{
                compareCallMethodAndField();
            }
            break;
            default:{

            }
            break;
        }
    }

    private void getClassObject(){
        Class<?> classObject = null;

        classObject = getClassObject_1();
        LogE("classObject_1 name : " + classObject.getName());
        classObject = getClassObject_2();
        LogE("classObject_2 name : " + classObject.getName());
        classObject = getClassObject_3();
        LogE("classObject_3 name : " + classObject.getName());
    }

    private void getMethodInfo(){
        getAllMethods();
        getCurrentClassMethods();
    }

    private void getFieldInfo(){
        getAllFields();
        getCurrentClassFields();
    }

    private void getSuperClass(){
        ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
        Class<?> superClass = programMonkey.getClass().getSuperclass();
        while (superClass != null) {
            LogE("programMonkey's super class is : " + superClass.getName());
            // 再獲取父類的上一層父類,直到最后的 Object 類,Object 的父類為 null
            superClass = superClass.getSuperclass();
        }
    }

    private void getInterfaces() {
        ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
        Class<?>[] interfaceses = programMonkey.getClass().getInterfaces();
        for (Class<?> class1 : interfaceses) {
            LogE("programMonkey's interface is : " + class1.getName());
        }
    }

    private void compareCallMethodAndField(){
        long callMethodCostTime = getCallMethodCostTime(10000);
        LogE("callMethodCostTime == " + callMethodCostTime);
        long callFieldCostTime = getCallFieldCostTime(10000);
        LogE("callFieldCostTime == " + callFieldCostTime);
    }

    private long getCallMethodCostTime(int count){
        long startTime = System.currentTimeMillis();
        for(int index = 0 ; index < count; index++){
            ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
            try{
                Method setmLanguageMethod = programMonkey.getClass().getMethod("setmLanguage", String.class);
                setmLanguageMethod.setAccessible(true);
                setmLanguageMethod.invoke(programMonkey, "Java");
            }catch(IllegalAccessException e){
                e.printStackTrace();
            }catch(InvocationTargetException e){
                e.printStackTrace();
            }catch(NoSuchMethodException e){
                e.printStackTrace();
            }
        }

        return System.currentTimeMillis()-startTime;
    }

    private long getCallFieldCostTime(int count){
        long startTime = System.currentTimeMillis();
        for(int index = 0 ; index < count; index++){
            ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
            try{
                Field ageField = programMonkey.getClass().getDeclaredField("mLanguage");
                ageField.set(programMonkey, "Java");
            }catch(NoSuchFieldException e){
                e.printStackTrace();
            }catch(IllegalAccessException e){
                e.printStackTrace();
            }
        }

        return System.currentTimeMillis()-startTime;
    }

    /**
     * 獲取當前類中的方法
     *
     * */
    private void getCurrentClassMethods() {
        ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
        Method[] methods = programMonkey.getClass().getDeclaredMethods();
        for (Method method : methods) {
            LogE("declared method name : " + method.getName());
        }

        try {
            Method getSalaryPerMonthMethod = programMonkey.getClass().getDeclaredMethod("getSalaryPerMonth");
            getSalaryPerMonthMethod.setAccessible(true);
            // 獲取返回類型
            Class<?> returnType = getSalaryPerMonthMethod.getReturnType();
            LogE("getSalaryPerMonth 方法的返回類型 : " + returnType.getName());

            // 獲取方法的參數類型列表
            Class<?>[] paramClasses = getSalaryPerMonthMethod.getParameterTypes() ;
            for (Class<?> class1 : paramClasses) {
                LogE("getSalaryPerMonth 方法的參數類型 : " + class1.getName());
            }

            // 是否是 private 函數,屬性是否是 private 也可以使用這種方式判斷
            LogE(getSalaryPerMonthMethod.getName() + " is private " + Modifier.isPrivate(getSalaryPerMonthMethod.getModifiers()));

            // 執行方法
            Object result = getSalaryPerMonthMethod.invoke(programMonkey);
            LogE("getSalaryPerMonth 方法的返回結果: " + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 獲取當前類和父類的所有方法
     *
     * */
    private void getAllMethods() {
        ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
        // 獲取當前類和父類的所有方法
        Method[] methods = programMonkey.getClass().getMethods();
        for (Method method : methods) {
            LogE("method name : " + method.getName());
        }

        try {
            Method setmLanguageMethod = programMonkey.getClass().getMethod("setmLanguage", String.class);
            setmLanguageMethod.setAccessible(true);

            // 獲取返回類型
            Class<?> returnType = setmLanguageMethod.getReturnType();
            LogE("setmLanguage 方法的返回類型 : " + returnType.getName());

            // 獲取方法的參數類型列表
            Class<?>[] paramClasses = setmLanguageMethod.getParameterTypes() ;
            for (Class<?> class1 : paramClasses) {
                LogE("setmLanguage 方法的參數類型 : " + class1.getName());
            }

            // 是否是 private 函數,屬性是否是 private 也可以使用這種方式判斷
            LogE(setmLanguageMethod.getName() + " is private " + Modifier.isPrivate(setmLanguageMethod.getModifiers()));

            // 執行方法
            Object result = setmLanguageMethod.invoke(programMonkey, "Java");
            LogE("setmLanguage 方法的返回結果: " + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private Class<?> getClassObject_1(){
        return ProgramMonkey.class;
    }

    private Class<?> getClassObject_2(){
        ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
        return programMonkey.getClass();
    }

    private Class<?> getClassObject_3(){
        try{
            return Class.forName("com.eebbk.reflectdemo.ProgramMonkey");
        }catch(ClassNotFoundException e){
            e.printStackTrace();
        }

        return null;
    }

    /**
     * 得到當前類的所有實例
     *
     * */
    private void getCurrentClassFields() {
        ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
        // 獲取當前類的所有屬性
        Field[] publicFields = programMonkey.getClass().getDeclaredFields();
        for (Field field : publicFields) {
            LogE("declared field name : " + field.getName());
        }

        try {
            // 獲取當前類的某個屬性
            Field ageField = programMonkey.getClass().getDeclaredField("mAge");
            // 獲取屬性值
            LogE(" my age is : " + ageField.getInt(programMonkey));
            // 設置屬性值
            ageField.set(programMonkey, 10);
            LogE(" my age is : " + ageField.getInt(programMonkey));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 得到當前類及其父類的所有實例
     *
     * */
    private void getAllFields() {
        ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
        // 獲取當前類和父類的所有屬性
        Field[] publicFields = programMonkey.getClass().getFields();
        for (Field field : publicFields) {
            LogE("field name : " + field.getName());
        }

        try {
            // 獲取當前類和父類的某個屬性
            Field ageField = programMonkey.getClass().getField("mAge");
            LogE(" age is : " + ageField.getInt(programMonkey));
            ageField.set(programMonkey, 8);
            LogE(" my age is : " + ageField.getInt(programMonkey));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void LogE(String msg){
        Log.e("Reflection", "============== " + msg);
    }
}
  • 演示結果:

三種獲取類信息的方式:

三種獲取類信息的方式

獲取當前類的方法、獲取當前類和父類的所有方法:

獲取當前類的方法、獲取當前類和父類的所有方法

獲取當前類的所有實例、獲取當前類和父類的所有實例:

獲取當前類的所有實例、獲取當前類和父類的所有實例

獲取父類信息:

獲取父類信息

獲取接口信息:

獲取接口信息

比較反射方法和實例的性能差異:

比較反射方法和實例的性能差異

通過上面的示例可以發現,通過反射能夠完成之前所描述的事情,并且反射方法比反射實例要慢很多。

反射的特點

優點

  • 靈活、自由度高:不受類的訪問權限限制,想對類做啥就做啥;

缺點

  • 性能問題:通過反射訪問、修改類的屬性和方法時會遠慢于直接操作,但性能問題的嚴重程度取決于在程序中是如何使用反射的。如果使用得很少,不是很頻繁,性能將不會是什么問題;

  • 安全性問題:反射可以隨意訪問和修改類的所有狀態和行為,破壞了類的封裝性,如果不熟悉被反射類的實現原理,隨意修改可能導致潛在的邏輯問題;

  • 兼容性問題:因為反射會涉及到直接訪問類的方法名和實例名,不同版本的API如果有變動,反射時找不到對應的屬性和方法時會報異常;

說明

  • 通過反射訪問方法比實例慢很多;

  • 有用到反射的類不能被混淆;

  • 反射存在性能問題,但使用不頻繁、按需使用時,對程序性能影響并不大;

  • 反射存在安全性問題,因為可以隨意修改類的所有狀態和行為(包括private方法和實例);

  • 使用反射訪問Android的API時需要注意因為不同API版本導致的兼容性問題;

參考資料

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

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,796評論 18 139
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,726評論 18 399
  • 今天電腦系統更新完以后突然很卡,不知道原因,eclipse 服務也啟動不起來了,就這樣別人都在干活,我一個人在這兒...
    魂歸瀟湘閱讀 142評論 0 0
  • 沒傘的挨著有傘的人走,靠得再近也躲不過雨;而無傘也有雨過天晴的時候,也能擁有屬于自己的陽光天地。
    呂明超閱讀 120評論 0 0