注解與反射

注解

聲明一個(gè)注解類型
public @interface Lance{
}
元注解

在定義注解時(shí)奖地,注解類也能夠使用其他的注解聲明。對(duì)注解類型進(jìn)行注解的注解類羹铅,我們稱之為 meta?annotation(元注解)香缺。

  • @Target
    注解標(biāo)記另一個(gè)注解奥额,以限制可以應(yīng)用注解的 Java 元素類型。目標(biāo)注解指定以下元素類型之一作為其值:
    ElementType.ANNOTATION_TYPE 可以應(yīng)用于注解類型访诱。
    ElementType.CONSTRUCTOR 可以應(yīng)用于構(gòu)造函數(shù)垫挨。
    ElementType.FIELD 可以應(yīng)用于字段或?qū)傩浴?br> ElementType.LOCAL_VARIABLE 可以應(yīng)用于局部變量。
    ElementType.METHOD 可以應(yīng)用于方法級(jí)注解触菜。
    ElementType.PACKAGE 可以應(yīng)用于包聲明九榔。
    ElementType.PARAMETER 可以應(yīng)用于方法的參數(shù)。
    ElementType.TYPE 可以應(yīng)用于類的任何元素涡相。
  • @Retention
    注解指定標(biāo)記注解的存儲(chǔ)方式:
    RetentionPolicy.SOURCE - 標(biāo)記的注解僅保留在源級(jí)別中哲泊,并被編譯器忽略。
    RetentionPolicy.CLASS - 標(biāo)記的注解在編譯時(shí)由編譯器保留催蝗,但 Java 虛擬機(jī)(JVM)會(huì)忽略切威。
    RetentionPolicy.RUNTIME - 標(biāo)記的注解由 JVM 保留,因此運(yùn)行時(shí)環(huán)境可以使用它丙号。

@Retention 三個(gè)值中 SOURCE < CLASS < RUNTIME先朦,即CLASS包含了SOURCE,RUNTIME包含SOURCE犬缨、
CLASS烙无。下文會(huì)介紹他們不同的應(yīng)用場(chǎng)景。

注解應(yīng)用場(chǎng)景

SOURCE

RetentionPolicy.SOURCE 遍尺,作用于源碼級(jí)別的注解截酷,可提供給IDE語(yǔ)法檢查、APT等場(chǎng)景使用乾戏。
在類中使用 SOURCE 級(jí)別的注解迂苛,其編譯之后的class中會(huì)被丟棄。

  • IDE語(yǔ)法檢查
    在Android開(kāi)發(fā)中鼓择, support-annotations 與 androidx.annotation) 中均有提供 @IntDef 注解三幻,此注解的定義如下:
@Retention(SOURCE) //源碼級(jí)別注解 
@Target({ANNOTATION_TYPE}) 
public @interface IntDef { 
int[] value() default {}; 
boolean flag() default false; 
boolean open() default false;
}

Java中Enum(枚舉)的實(shí)質(zhì)是特殊單例的靜態(tài)成員變量,在運(yùn)行期所有枚舉類作為單例呐能,全部加載到內(nèi)存中念搬。
比常量多5到10倍的內(nèi)存占用。此注解的意義在于能夠取代枚舉摆出,實(shí)現(xiàn)如方法入?yún)⑾拗啤?/strong>
如:我們定義方法 test 朗徊,此方法接收參數(shù) teacher 需要在:Lance、Alvin中選擇一個(gè)偎漫。如果使用枚舉能夠?qū)崿F(xiàn)為:

public enum Teacher{
LANCE,ALVIN
}
public void test(Teacher teacher) {
}

而現(xiàn)在為了進(jìn)行內(nèi)存優(yōu)化爷恳,我們現(xiàn)在不再使用枚舉,則方法定義為:

public static final int LANCE = 1; 
public static final int ALVIN = 2; 
public void test(int teacher) {
}

然而此時(shí)象踊,調(diào)用 test 方法由于采用基本數(shù)據(jù)類型int温亲,將無(wú)法進(jìn)行類型限定棚壁。此時(shí)使用@IntDef增加自定義注解:

public static final int LANCE = 1; 
public static final int ALVIN = 2; 
@IntDef(value = {MAN, WOMEN}) //限定為L(zhǎng)ANCE,ALVIN @Target(ElementType.PARAMETER) //作用于參數(shù)的注解 @Retention(RetentionPolicy.SOURCE) //源碼級(jí)別注解 
public @interface Teacher { 
}
public void test(@Teacher int teacher) { 
}

此時(shí)栈虚,我們?cè)偃フ{(diào)用 test 方法袖外,如果傳遞的參數(shù)不是 LANCE 或者 ALVIN 則會(huì)顯示 Inspection 警告(編譯不會(huì)報(bào)錯(cuò))。

以上注解均為 SOURCE 級(jí)別魂务,本身IDEA/AS 就是由Java開(kāi)發(fā)的在刺,工具實(shí)現(xiàn)了對(duì)Java語(yǔ)法的檢查,借助注解能對(duì)被注解的特定語(yǔ)法進(jìn)行額外檢查头镊。

  • APT注解處理器

APT全稱為:"Anotation Processor Tools"蚣驼,意為注解處理器。顧名思義相艇,其用于處理注解颖杏。編寫好的Java源文件,需要經(jīng)過(guò) javac 的編譯坛芽,翻譯為虛擬機(jī)能夠加載解析的字節(jié)碼Class文件留储。注解處理器是 javac 自帶的一個(gè)工具,用來(lái)在編譯時(shí)期掃描處理注解信息咙轩。你可以為某些注解注冊(cè)自己的注解處理器获讳。 注冊(cè)的注解處理器由 javac調(diào)起,并將注解信息傳遞給注解處理器進(jìn)行處理活喊。
注解處理器是對(duì)注解應(yīng)用最為廣泛的場(chǎng)景丐膝。在Glide、EventBus3钾菊、Butterknifer帅矗、Tinker、ARouter等等常用框架中都有注解處理器的身影煞烫。但是你可能會(huì)發(fā)現(xiàn)浑此,這些框架中對(duì)注解的定義并不是 SOURCE 級(jí)別,更多的是 CLASS 級(jí)別滞详,別忘了:CLASS包含了SOURCE凛俱,RUNTIME包含SOURCE、CLASS料饥。
關(guān)于注解處理器的實(shí)現(xiàn)蒲犬,在后續(xù)課程中會(huì)有相當(dāng)多的介紹。此處先不進(jìn)行詳細(xì)介紹稀火。

CLASS

定義為 CLASS 的注解暖哨,會(huì)保留在class文件中赌朋,但是會(huì)被虛擬機(jī)忽略(即無(wú)法在運(yùn)行期反射獲取注解)凰狞。此時(shí)完全符合此種注解的應(yīng)用場(chǎng)景為字節(jié)碼操作篇裁。如:AspectJ、熱修復(fù)Roubust中應(yīng)用此場(chǎng)景赡若。
所謂字節(jié)碼操作即為达布,直接修改字節(jié)碼Class文件以達(dá)到修改代碼執(zhí)行邏輯的目的。在程序中有多處需要進(jìn)行是否登錄的判斷逾冬。


image.png

如果我們使用普通的編程方式黍聂,需要在代碼中進(jìn)行 if-else 的判斷,也許存在十個(gè)判斷點(diǎn)身腻,則需要在每個(gè)判斷點(diǎn)加入此項(xiàng)判斷产还。此時(shí),我們可以借助AOP(面向切面)編程思想嘀趟,將程序中所有功能點(diǎn)劃分為: 需要登錄 與 無(wú)需登錄兩種類型脐区,即兩個(gè)切面。對(duì)于切面的區(qū)分即可采用注解她按。

//Java源碼 
@Target(ElementType.METHOD) 
@Retention(RetentionPolicy.CLASS) 
public @interface Login {
}
@Login 
public void jumpA(){ 
 startActivity(new Intent(this,AActivity.class)); 
}
public void jumpB(){ 
 startActivity(new Intent(this,BActivity.class)); 
}

在上訴代碼中牛隅, jumpA 方法需要具備登錄身份。而 Login 注解的定義被設(shè)置為 CLASS 酌泰。因此我們能夠在該類所編譯的字節(jié)碼中獲得到方法注解 Login 媒佣。在操作字節(jié)碼時(shí),就能夠根據(jù)方法是否具備該注解來(lái)修改class中該方法的內(nèi)容加入 if-else 的代碼段:

//Class字節(jié)碼 
@Login public void jumpA() { 
 if (this.isLogin) { 
   this.startActivity(new Intent(this, LoginActivity.class)); 
 } else { 
   this.startActivity(new Intent(this, AActivity.class)); 
 } 
}

public void jumpB() { 
 startActivity(new Intent(this,BActivity.class)); 
}

注解能夠設(shè)置類型元素(參數(shù))陵刹,結(jié)合參數(shù)能實(shí)現(xiàn)更為豐富的場(chǎng)景默伍,如:運(yùn)行期權(quán)限判定等。

RUNTIME

注解保留至運(yùn)行期衰琐,意味著我們能夠在運(yùn)行期間結(jié)合反射技術(shù)獲取注解中的所有信息巡验。

反射

一般情況下,我們使用某個(gè)類時(shí)必定知道它是什么類碘耳,是用來(lái)做什么的显设,并且能夠獲得此類的引用。于是我們直接對(duì)這個(gè)類進(jìn)行實(shí)例化辛辨,之后使用這個(gè)類對(duì)象進(jìn)行操作捕捂。

反射則是一開(kāi)始并不知道我要初始化的類對(duì)象是什么,自然也無(wú)法使用 new 關(guān)鍵字來(lái)創(chuàng)建對(duì)象了斗搞。這時(shí)候指攒,我們使用 JDK 提供的反射 API 進(jìn)行反射調(diào)用。

反射就是在運(yùn)行狀態(tài)中,對(duì)于任意一個(gè)類,都能夠知道這個(gè)類的所有屬性和方法;對(duì)于任意一個(gè)對(duì)象,都能夠調(diào)用它的任意方法和屬性;并且能改變它的屬性僻焚。是Java被視為動(dòng)態(tài)語(yǔ)言的關(guān)鍵允悦。

Java反射機(jī)制主要提供了以下功能:

  • 在運(yùn)行時(shí)構(gòu)造任意一個(gè)類的對(duì)象
  • 在運(yùn)行時(shí)獲取或者修改任意一個(gè)類所具有的成員變量和方法
  • 在運(yùn)行時(shí)調(diào)用任意一個(gè)對(duì)象的方法(屬性)
Gson反序列化
static class Response<T> { 
 T data; 
 int code; 
 String message; 
 @Override 
 public String toString() { 
   return "Response{" + "data=" + data + ", code=" + code + ", message='" + message + '\'' + '}'; 
 }
 public Response(T data, int code, String message) { 
   this.data = data; 
   this.code = code; 
   this.message = message; 
 } 
}
static class Data { 
 String result; 
 public Data(String result) { 
   this.result = result; 
 }

 @Override 
 public String toString() { 
   return "Data{" + "result=" + result + '}'; 
 } 
}
 public static void main(String[] args) { 
   Response<Data> dataResponse = new Response(new Data("數(shù)據(jù)"), 1, "成功");  
   Gson gson = new Gson(); String json = gson.toJson(dataResponse);   
   System.out.println(json);
   //為什么TypeToken要定義為抽象類?
   Response<Data> resp = gson.fromJson(json,   new TypeToken<Response<Data>>() { }.getType());   
   System.out.println(resp.data.result); 
}

在進(jìn)行GSON反序列化時(shí)虑啤,存在泛型時(shí)隙弛,可以借助 TypeToken 獲取Type以完成泛型的反序列化架馋。但是為什么TypeToken 要被定義為抽象類呢?
因?yàn)橹挥卸x為抽象類或者接口全闷,這樣在使用時(shí)叉寂,需要?jiǎng)?chuàng)建對(duì)應(yīng)的實(shí)現(xiàn)類,此時(shí)確定泛型類型总珠,編譯才能夠?qū)⒎盒蛃ignature信息記錄到Class元數(shù)據(jù)中屏鳍。

控件綁定

/**
*
*/
public class InjectViewUtils {

   public static void injectView(Activity activity) {
       if (null == activity) return;

       Class<? extends Activity> activityClass = activity.getClass();
       Field[] declaredFields = activityClass.getDeclaredFields();
       for (Field field : declaredFields) {//獲取Activity類里面聲明的所有成員變量
           if (field.isAnnotationPresent(InjectView.class)) {//找出標(biāo)注了@InjectView的成員變量
               //解析InjectView 獲取button id
               InjectView injectView = field.getAnnotation(InjectView.class);
               int value = injectView.value();//獲取變量的值,也就是控件的id

               try {
                   //找到findViewById方法
                   Method findViewById = activityClass.getMethod("findViewById", int.class);
                   findViewById.setAccessible(true);
                   Object view = findViewById.invoke(activity, value);
                   field.set(activity, view);
               } catch (Exception e) {
                   e.printStackTrace();
               }
           }
       } 
   }

   public static void injectAutowired(Activity activity) {
       Class<? extends Activity> cls = activity.getClass();
       //獲得數(shù)據(jù)
       Intent intent = activity.getIntent();
       Bundle extras = intent.getExtras();
       if (extras == null) {
           return;
       }

       //獲得此類所有的成員
       Field[] declaredFields = cls.getDeclaredFields();
       for (Field field : declaredFields) {
           if (field.isAnnotationPresent(Autowired.class)) {
               Autowired autowired = field.getAnnotation(Autowired.class);
               //獲得key
               String key = TextUtils.isEmpty(autowired.value()) ? field.getName() : autowired.value();

               if (extras.containsKey(key)) {
                   Object obj = extras.get(key);
                   // todo Parcelable數(shù)組類型不能直接設(shè)置局服,其他的都可以.
                   //獲得數(shù)組單個(gè)元素類型
                   Class<?> componentType = field.getType().getComponentType();
                   //當(dāng)前屬性是數(shù)組并且是 Parcelable(子類)數(shù)組
                   if (field.getType().isArray() &&
                           Parcelable.class.isAssignableFrom(componentType)) {
                       Object[] objs = (Object[]) obj;
                       //創(chuàng)建對(duì)應(yīng)類型的數(shù)組并由objs拷貝

                       Object[] objects = Arrays.copyOf(objs, objs.length, (Class<? extends Object[]>) field.getType());
                       obj = objects;
                   }

                   field.setAccessible(true);
                   try {
                       field.set(activity, obj);
                   } catch (IllegalAccessException e) {
                       e.printStackTrace();
                   }
               }
           }
       }
   } 
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末钓瞭,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子淫奔,更是在濱河造成了極大的恐慌降淮,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搏讶,死亡現(xiàn)場(chǎng)離奇詭異佳鳖,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)媒惕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門系吩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人妒蔚,你說(shuō)我怎么就攤上這事穿挨。” “怎么了肴盏?”我有些...
    開(kāi)封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵科盛,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我菜皂,道長(zhǎng)贞绵,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任恍飘,我火速辦了婚禮榨崩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘章母。我一直安慰自己母蛛,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布乳怎。 她就那樣靜靜地躺著彩郊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上秫逝,一...
    開(kāi)封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天违寿,我揣著相機(jī)與錄音狐肢,去河邊找鬼椎镣。 笑死蕊苗,一個(gè)胖子當(dāng)著我的面吹牛柿赊,可吹牛的內(nèi)容都是我干的趋急。 我是一名探鬼主播蒋川,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼廉油,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼惠险!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起抒线,我...
    開(kāi)封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤班巩,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后嘶炭,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體抱慌,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年眨猎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了抑进。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡睡陪,死狀恐怖寺渗,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情兰迫,我是刑警寧澤信殊,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站汁果,受9級(jí)特大地震影響涡拘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜据德,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一鲸伴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧晋控,春花似錦汞窗、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春裹唆,著一層夾襖步出監(jiān)牢的瞬間誓斥,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工许帐, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留劳坑,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓成畦,卻偏偏與公主長(zhǎng)得像距芬,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子循帐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容