Java 反射最佳實踐

tianzhijiexian發表於2015-09-23

概要:最簡單優雅的使用反射。

本文的例子都可以在示例程式碼中看到並下載,如果喜歡請star,如果覺得有紕漏請提交issue,如果你有更好的點子可以提交pull request。本文的示例程式碼主要是基於jOOR行編寫的,如果想了解更多請檢視這裡的測試程式碼。

一、需求

今天一個“懶”程式設計師突然跑過來說:“反射好麻煩,我要提點需求。”

聽到這句話後我就知道,今天一定不好過了,奇葩需求又來了。

我們之前寫反射都是要這麼寫:

public static <T> T create(HttpRequest httpRequest) {
        Object httpRequestEntity = null;
        try {
            Class<T> httpRequestEntityCls = (Class<T>) Class.forName(HttpProcessor.PACKAGE_NAME + "." + HttpProcessor.CLASS_NAME);
            Constructor con = httpRequestEntityCls.getConstructor(HttpRequest.class);
            httpRequestEntity = con.newInstance(httpRequest);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return (T) httpRequestEntity;
    }

因為反射在開發中很少用(做普通的業務開發的話),僅僅在自己寫一些框架和註解框架時會用到,所以對api總是不熟悉。每次用到api都要去網上查,查了後又得自己實驗下,很不爽。更差勁的是這樣寫法可讀性十分低下。我不希望這樣寫反射,我希望反射能像

String str = new String();

這樣簡單,一行程式碼搞定!

此外,有很多人都說反射影響效能,在開發的時候要避免用反射。那麼什麼時候該用反射,什麼時候不用反射呢?用什麼方式來避免反射呢?如果不明白什麼時候用反射,就很難將反射活學活用了。

二、分析

當我們接到上面需求後,我長舒一口氣,因為這回的需求還比較簡單。

我相信有人會說:“反射就那幾個api,一直沒變過,你就不會自己去查啊,覺得麻煩就別寫程式碼啊,不知道反射的內部細節你怎麼去提高呢?”
其實不然,重複寫麻煩的程式碼和提高自己的程式碼能力是完全無關的,我實在不知道我們寫了成千上萬行的findViewById後除了知道類要和xml檔案繫結外,還學到了什麼。

那麼這回我們繼續來挑戰傳統思維和模板式程式碼,來看看新一代的反射程式碼應該怎麼寫,如何用一行程式碼來反射出類。
在做之前,來看看我們一般用反射來幹嘛?

1. 反射構建出無法直接訪問的類 

2. set或get到無法訪問的類變數 

3. 呼叫不可訪問的方法

三、解決方案

3.1 一行程式碼寫反射

作為一個Android程式設計師,索性就拿TextView這個類開刀吧。首先定義一個類變數:

TextView mTv;

通過反射得到例項:

// 有引數,建立類
mTv = Reflect.on(TextView.class).create(this).get();

// 通過類全名得到類
String word = Reflect.on("java.lang.String").create("Reflect TextView").get();

// 無引數,建立類
Fragment fragment = Reflect.on(Fragment.class).create().get();

通過反射呼叫方法:

// 呼叫無引數方法
L.d("call getText() : " + Reflect.on(mTv).call("getText").toString());

// 呼叫有引數方法
Reflect.on(mTv).call("setTextColor", 0xffff0000);

通過反射get、set類變數

TextView中有個mText變數,來看看我們怎麼接近它。

// 設定引數
Reflect.on(mTv).set("mText", "---------- new Reflect TextView ----------");

// 獲得引數
L.d("setgetParam is " + Reflect.on(mTv).get("mText"));

3.2 什麼時候該用反射,什麼時候不用反射

又到了這樣權衡利弊的時候了,首先我們明確,在日常開發中儘量不要用反射,除非遇到了必須要通過反射才能呼叫的方法。比如我在做一個下拉通知中心功能的時候就遇到了這樣的情況。系統沒有提供api,所以我們只能通過反射進行呼叫,所以我自己寫了這樣一段程式碼:

<uses-permission android:name="android.permission.EXPAND_STATUS_BAR"/>
private static void doInStatusBar(Context mContext, String methodName) {
        try {
            Object service = mContext.getSystemService("statusbar");
            Method expand = service.getClass().getMethod(methodName);
            expand.invoke(service);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 顯示訊息中心
     */
    public static void openStatusBar(Context mContext) {
        // 判斷系統版本號
        String methodName = (VERSION.SDK_INT <= 16) ? "expand" : "expandNotificationsPanel";
        doInStatusBar(mContext, methodName);
    }

    /**
     * 關閉訊息中心
     */
    public static void closeStatusBar(Context mContext) {
        // 判斷系統版本號
        String methodName = (VERSION.SDK_INT <= 16) ? "collapse" : "collapsePanels";
        doInStatusBar(mContext, methodName);
    }

先來看看利用jOOR寫的doInStatusBar方法會簡潔到什麼程度:

private static void doInStatusBar(Context mContext, String methodName) {
        Object service = mContext.getSystemService("statusbar");
        Reflect.on(service).call(methodName);
    }

哇,就一行程式碼啊,很爽吧~

爽完了,我們就來看看反射問題吧。因為不是系統給出的api,所以谷歌在不同的版本上用了不同的方法名來做處理,用反射的話我們就必須進行版本的判斷,這是需要注意的,此外反射在效能方面確實不好,這裡需要謹慎。

我的建議:

如果一個類中有很多地方都是private的,而你的需求都需要依賴這些方法或者變數,那麼比起用反射,推薦把這個類複製出來,變成自己的類,像是toolbar這樣的類就可以進行這樣的操作。

在自己寫框架的時候,我們肯定會用到反射,很簡單的例子就是事件匯流排和註解框架,翔哥就說過一句話:無反射,無框架。也正因為是自己寫的框架,所以通過反射呼叫的方法名和引數一般不會變,更何況做執行時註解框架的話,反射肯定會出現。在這種情況下千萬不要害怕反射,索性放心大膽的做。因為它會讓你完成很多不可能完成的任務。

總結下來就是:

實際進行日常開發的時候儘量少用反射,可以通過複製原始類的形式來避免反射。在寫框架時,不避諱反射,在關鍵時利用反射來助自己一臂之力。

四、後記

我們終於完成了用一行程式碼寫反射,避免了很多無意義的模板式程式碼。需要再次說明的是,本文是依據jOOR 進行編寫的,這裡有原專案readme的中文翻譯。

jOOR是我無意中遇到的開源庫,第一次見到它時我就知道這個是我想要的,因為那時候我被反射搞的很亂,而它簡潔的編碼方式給我帶來了新的思考,大大提高了程式碼可讀性。順便一說,作者人比較好(就是死活不願意讓我放入中文的readme),技術也很不錯。該專案有很詳細的測試用例,並且還給出了幾個類似的反射呼叫封裝庫。可見作者在寫庫時做了大量的調研和測試工作,讓我們可以放心的運用該庫(其實就兩個類)

本文希望帶給大家一個反射的新思路,給出一個最簡單實用的反射寫法,希望能被大家迅速運用到實踐中去。更加重要的是,通過對jOOR的分析,讓我知道了寫庫前應該調研類似的庫,而不是完全的創造新輪子,調研和測試是程式碼穩定性的重要保障。

參考自

http://www.cnblogs.com/tianzhijiexian/p/3906774.html
https://github.com/tianzhijiexian/HttpAnnotation/blob/master/lib/src/main/java/kale/net/http/util/HttpReqAdapter.java

相關文章