Java反射最佳實(shí)踐
本文的例子都可以在示例代碼中看到并下載,如果喜歡請(qǐng)star,如果覺(jué)得有紕漏請(qǐng)?zhí)峤籭ssue,如果你有更好的點(diǎn)子可以提交pull request。本文的示例代碼主要是基于jOOR行編寫(xiě)的,如果想了解更多請(qǐng)查看這里的測(cè)試代碼。
一、需求
今天一個(gè)“懶”程序員突然跑過(guò)來(lái)說(shuō):“反射好麻煩,我要提點(diǎn)需求。”
聽(tīng)到這句話(huà)后我就知道,今天一定不好過(guò)了,奇葩需求又來(lái)了。
我們之前寫(xiě)反射都是要這么寫(xiě):
- 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;
- }
因?yàn)榉瓷湓陂_(kāi)發(fā)中很少用(做普通的業(yè)務(wù)開(kāi)發(fā)的話(huà)),僅僅在自己寫(xiě)一些框架和注解框架時(shí)會(huì)用到,所以對(duì)api總是不熟悉。每次用到api都要去網(wǎng)上查,查了后又得自己實(shí)驗(yàn)下,很不爽。更差勁的是這樣寫(xiě)法可讀性十分低下。我不希望這樣寫(xiě)反射,我希望反射能像
- String str = new String();
這樣簡(jiǎn)單,一行代碼搞定!。
此外,有很多人都說(shuō)反射影響性能,在開(kāi)發(fā)的時(shí)候要避免用反射。那么什么時(shí)候該用反射,什么時(shí)候不用反射呢?用什么方式來(lái)避免反射呢?如果不明白什么時(shí)候用反射,就很難將反射活學(xué)活用了。
二、分析
當(dāng)我們接到上面需求后,我長(zhǎng)舒一口氣,因?yàn)檫@回的需求還比較簡(jiǎn)單。
我相信有人會(huì)說(shuō):“反射就那幾個(gè)api,一直沒(méi)變過(guò),你就不會(huì)自己去查啊,覺(jué)得麻煩就別寫(xiě)代碼啊,不知道反射的內(nèi)部細(xì)節(jié)你怎么去提高呢?”
其實(shí)不然,重復(fù)寫(xiě)麻煩的代碼和提高自己的代碼能力是完全無(wú)關(guān)的,我實(shí)在不知道我們寫(xiě)了成千上萬(wàn)行的findViewById后除了知道類(lèi)要和xml文件綁定外,還學(xué)到了什么。
那么這回我們繼續(xù)來(lái)挑戰(zhàn)傳統(tǒng)思維和模板式代碼,來(lái)看看新一代的反射代碼應(yīng)該怎么寫(xiě),如何用一行代碼來(lái)反射出類(lèi)。
在做之前,來(lái)看看我們一般用反射來(lái)干嘛?
- 反射構(gòu)建出無(wú)法直接訪問(wèn)的類(lèi)
- set或get到無(wú)法訪問(wèn)的類(lèi)變量
- 調(diào)用不可訪問(wèn)的方法
三、解決方案
3.1 一行代碼寫(xiě)反射
作為一個(gè)Android程序員,索性就拿TextView這個(gè)類(lèi)開(kāi)刀吧。首先定義一個(gè)類(lèi)變量:
- TextView mTv;
通過(guò)反射得到實(shí)例:
- // 有參數(shù),建立類(lèi)
- mTv = Reflect.on(TextView.class).create(this).get();
- // 通過(guò)類(lèi)全名得到類(lèi)
- String word = Reflect.on("java.lang.String").create("Reflect TextView").get();
- // 無(wú)參數(shù),建立類(lèi)
- Fragment fragment = Reflect.on(Fragment.class).create().get();
通過(guò)反射調(diào)用方法:
- // 調(diào)用無(wú)參數(shù)方法
- L.d("call getText() : " + Reflect.on(mTv).call("getText").toString());
- // 調(diào)用有參數(shù)方法
- Reflect.on(mTv).call("setTextColor", 0xffff0000);
通過(guò)反射get、set類(lèi)變量
TextView中有個(gè)mText變量,來(lái)看看我們?cè)趺唇咏?/p>
- // 設(shè)置參數(shù)
- Reflect.on(mTv).set("mText", "---------- new Reflect TextView ----------");
- // 獲得參數(shù)
- L.d("setgetParam is " + Reflect.on(mTv).get("mText"));
3.2 什么時(shí)候該用反射,什么時(shí)候不用反射
又到了這樣權(quán)衡利弊的時(shí)候了,首先我們明確,在日常開(kāi)發(fā)中盡量不要用反射,除非遇到了必須要通過(guò)反射才能調(diào)用的方法。比如我在做一個(gè)下拉通知中心功能的時(shí)候就遇到了這樣的情況。系統(tǒng)沒(méi)有提供api,所以我們只能通過(guò)反射進(jìn)行調(diào)用,所以我自己寫(xiě)了這樣一段代碼:
- <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) {
- // 判斷系統(tǒng)版本號(hào)
- String methodName = (VERSION.SDK_INT <= 16) ? "expand" : "expandNotificationsPanel";
- doInStatusBar(mContext, methodName);
- }
- /**
- * 關(guān)閉消息中心
- */
- public static void closeStatusBar(Context mContext) {
- // 判斷系統(tǒng)版本號(hào)
- String methodName = (VERSION.SDK_INT <= 16) ? "collapse" : "collapsePanels";
- doInStatusBar(mContext, methodName);
- }
先來(lái)看看利用jOOR寫(xiě)的doInStatusBar方法會(huì)簡(jiǎn)潔到什么程度:
- private static void doInStatusBar(Context mContext, String methodName) {
- Object service = mContext.getSystemService("statusbar");
- Reflect.on(service).call(methodName);
- }
哇,就一行代碼啊,很爽吧~
爽完了,我們就來(lái)看看反射問(wèn)題吧。因?yàn)椴皇窍到y(tǒng)給出的api,所以谷歌在不同的版本上用了不同的方法名來(lái)做處理,用反射的話(huà)我們就必須進(jìn)行版本的判斷,這是需要注意的,此外反射在性能方面確實(shí)不好,這里需要謹(jǐn)慎。
我的建議:
如果一個(gè)類(lèi)中有很多地方都是private的,而你的需求都需要依賴(lài)這些方法或者變量,那么比起用反射,推薦把這個(gè)類(lèi)復(fù)制出來(lái),變成自己的類(lèi),像是toolbar這樣的類(lèi)就可以進(jìn)行這樣的操作。
在自己寫(xiě)框架的時(shí)候,我們肯定會(huì)用到反射,很簡(jiǎn)單的例子就是事件總線和注解框架,翔哥就說(shuō)過(guò)一句話(huà):無(wú)反射,無(wú)框架。也正因?yàn)槭亲约簩?xiě)的框架,所以通過(guò)反射調(diào)用的方法名和參數(shù)一般不會(huì)變,更何況做運(yùn)行時(shí)注解框架的話(huà),反射肯定會(huì)出現(xiàn)。在這種情況下千萬(wàn)不要害怕反射,索性放心大膽的做。因?yàn)樗鼤?huì)讓你完成很多不可能完成的任務(wù)。
總結(jié)下來(lái)就是:
實(shí)際進(jìn)行日常開(kāi)發(fā)的時(shí)候盡量少用反射,可以通過(guò)復(fù)制原始類(lèi)的形式來(lái)避免反射。在寫(xiě)框架時(shí),不避諱反射,在關(guān)鍵時(shí)利用反射來(lái)助自己一臂之力。
四、后記
我們終于完成了用一行代碼寫(xiě)反射,避免了很多無(wú)意義的模板式代碼。需要再次說(shuō)明的是,本文是依據(jù)jOOR 進(jìn)行編寫(xiě)的,這里有原項(xiàng)目readme的中文翻譯。
jOOR是我無(wú)意中遇到的開(kāi)源庫(kù),***次見(jiàn)到它時(shí)我就知道這個(gè)是我想要的,因?yàn)槟菚r(shí)候我被反射搞的很亂,而它簡(jiǎn)潔的編碼方式給我?guī)?lái)了新的思考,大大提高了代碼可讀性。順便一說(shuō),作者人比較好(就是死活不愿意讓我放入中文的readme),技術(shù)也很不錯(cuò)。該項(xiàng)目有很詳細(xì)的測(cè)試用例,并且還給出了幾個(gè)類(lèi)似的反射調(diào)用封裝庫(kù)??梢?jiàn)作者在寫(xiě)庫(kù)時(shí)做了大量的調(diào)研和測(cè)試工作,讓我們可以放心的運(yùn)用該庫(kù)(其實(shí)就兩個(gè)類(lèi))。
本文希望帶給大家一個(gè)反射的新思路,給出一個(gè)最簡(jiǎn)單實(shí)用的反射寫(xiě)法,希望能被大家迅速運(yùn)用到實(shí)踐中去。更加重要的是,通過(guò)對(duì)jOOR的分析,讓我知道了寫(xiě)庫(kù)前應(yīng)該調(diào)研類(lèi)似的庫(kù),而不是完全的創(chuàng)造新輪子,調(diào)研和測(cè)試是代碼穩(wěn)定性的重要保障。