Android APT 系列 (二):APT 筑基之注解

注解介紹
元數(shù)據(jù)

元數(shù)據(jù)就是為其他數(shù)據(jù)提供信息的數(shù)據(jù)

注解

官方解釋:注解用于為代碼提供元數(shù)據(jù)炼杖。作為元數(shù)據(jù)蒂教,注解不直接影響你的代碼執(zhí)行蜓氨,但也有一些類型的注解實際上可以用于這一目的月杉。Java 注解是從 JDK 1.5 開始添加到 Java 的盗痒。

簡單的理解:注解就是附加到代碼上的一種額外補(bǔ)充信息

注解作用

源碼階段注解: 編譯器可利用該階段注解檢測錯誤横漏,提示警告信息谨设,打印日志等
編譯階段注解:利用注解信息自動生成代碼、文檔或者做其它相應(yīng)的自動處理
運(yùn)行階段注解: 可通過反射獲取注解信息缎浇,做相應(yīng)操作

如何自定義定義一個注解

使用 @interface+ 注解名稱這種語法結(jié)構(gòu)就能定義一個注解扎拣,如下:

@interface TestAnnotation{

}
元注解

元注解就是為注解提供注解的注解

JDK 給我們提供的元注解
1、@Target
2、@Retention
3二蓝、@Inherited
4誉券、@Documented
5、@Repeatable

@Target

@Target表示這個注解能放在什么位置上

ElementType.ANNOTATION_TYPE //能修飾注解
ElementType.CONSTRUCTOR //能修飾構(gòu)造器
ElementType.FIELD //能修飾成員變量
ElementType.LOCAL_VARIABLE //能修飾局部變量
ElementType.METHOD //能修飾方法
ElementType.PACKAGE //能修飾包名 
ElementType.PARAMETER //能修飾參數(shù)
ElementType.TYPE //能修飾類刊愚、接口或枚舉類型
ElementType.TYPE_PARAMETER //能修飾泛型踊跟,如泛型方法、泛型類百拓、泛型接口 (jdk1.8加入)
ElementType.TYPE_USE //能修飾類型 可用于任意類型除了 class (jdk1.8加入)
  
@Target(ElementType.TYPE)
@interface TestAnnotation{
    
}

注意:默認(rèn)情況下無限制

@Retention

@Retention 表示注解的的生命周期琴锭,可選的值有 3 個:

RetentionPolicy.SOURCE //表示注解只在源碼中存在,編譯成 class 之后衙传,就沒了
  
RetentionPolicy.CLASS //表示注解在 java 源文件編程成 .class 文件后决帖,依然存在,但是運(yùn)行起來后就沒了
  
RetentionPolicy.RUNTIME //表示注解在運(yùn)行起來后依然存在蓖捶,程序可以通過反射獲取這些信息
  
@Retention(RetentionPolicy.RUNTIME)
@interface TestAnnotation{

}

注意:默認(rèn)情況下為 RetentionPolicy.CLASS

@Inherited

@Inherited表示該注解可被繼承地回,即當(dāng)一個子類繼承一個父類,該父類添加的注解有被 @Inherited修飾俊鱼,那么子類就可以獲取到該注解刻像,否則獲取不到

@Inherited
@interface TestAnnotation{

}

注意:默認(rèn)情況下為不可繼承

@Documented

@Documented 表示該注解在通過javadoc 命令生成Api文檔后,會出現(xiàn)該注解的注釋說明

@Documented
@interface TestAnnotation{

}

注意:默認(rèn)情況下為不出現(xiàn)

@Repeatable

@RepeatableJDK 1.8新增的元注解并闲,它表示注解在同一個位置能出現(xiàn)多次细睡,這個注解有點抽象,我們通過一個實際例子理解一下

//游戲玩家注解
@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface GamePlayer{
    Game[] value();
}

//游戲注解
@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(GamePlayer.class)
@interface Game{
    String gameName();
}

@Game(gameName = "CF")
@Game(gameName = "LOL")
@Game(gameName = "DNF")
class GameTest{

}

注意:默認(rèn)情況下不可重復(fù)
經(jīng)驗:通常情況下帝火,我們會使用多個元注解組合來修飾自定義注解

注解屬性
注解屬性類型

1溜徙、基本數(shù)據(jù)類型
2、String
3犀填、枚舉類型
4蠢壹、注解類型
5、Class 類型
6九巡、以上類型的一維數(shù)組類型

定義注解屬性

首先我們定義一些注解屬性图贸,如下:

@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface TestAnnotation{
    //這就是注解屬性的語法結(jié)構(gòu)
    //定義一個屬性并給了默認(rèn)值
    String name() default "erdai";
  
    //定義一個屬性未給默認(rèn)值
    int age();
}

可能你會有些疑問:這難道不是在定義方法嗎?還可以給默認(rèn)值冕广?
這些疑問先留著疏日,我們繼續(xù)分析
自定義注解默認(rèn)都會繼承 AnnotationAnnotation 是一個接口撒汉,源碼如下:

public interface Annotation {
   
    boolean equals(Object obj);

    int hashCode();
  
    String toString();
    
    Class<? extends Annotation> annotationType();
}

我們知道沟优,在接口中可以定義屬性和方法,那么作為自定義注解神凑,是否也可以定義呢净神?
可以何吝,接口中的屬性默認(rèn)都是用public static final 修飾的,默認(rèn)是一個常量鹃唯,對于自定義注解來說爱榕,這點沒有任何區(qū)別。而接口中的方法其實就相當(dāng)于自定義注解的屬性坡慌,只不過自定義注解還可以給默認(rèn)值黔酥。因此我們在學(xué)習(xí)自定義注解屬性時,我們應(yīng)該把它當(dāng)作一個新知識洪橘,加上我剛才對接口的分析對比跪者,你上面的那些疑問便可以迎刃而解了

注解屬性使用

1、在使用注解的后面接上一對括號熄求,括號里面使用 屬性名 = value的格式渣玲,多個屬性之間中間用 ,隔開
2、未給默認(rèn)值的屬性必須進(jìn)行賦值弟晚,否則編譯器會報紅

//單個屬性
@TestAnnotation(age = 18)
class Test{
    
}

//多個屬性
@TestAnnotation(age = 18,name = "erdai666")
class Test{

}
注解屬性獲取

1忘衍、我們在獲取屬性的時候,可以先判斷一下是否存在該注解卿城,增強(qiáng)代碼的健壯性枚钓,如下:

@TestAnnotation(age = 18,name = "erdai666")
class Test{

}

Class<Test> testClass = Test.class;
//獲取當(dāng)前注解是否存在
boolean annotationPresent = testClass.isAnnotationPresent(TestAnnotation.class);
//如果存在則進(jìn)入條件體
if(annotationPresent){
    TestAnnotation declaredAnnotation = testClass.getDeclaredAnnotation(TestAnnotation.class);
    System.out.println(declaredAnnotation.name());
    System.out.println(declaredAnnotation.age());
}

2、獲取類屬性的注解屬性

@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface TestField{
    String filed();
}

class Test{
    @TestField(filed = "我是屬性")
    public String test;
}

//通過反射獲取屬性注解
Class<Test> testClass1 = Test.class;
try {
    Field field = testClass1.getDeclaredField("test");
    if(field.isAnnotationPresent(TestField.class)){
        TestField fieldAnnotation = field.getDeclaredAnnotation(TestField.class);
        System.out.println(fieldAnnotation.filed());
    }
} catch (NoSuchFieldException e) {
    e.printStackTrace();
}
//打印結(jié)果
我是屬性

3瑟押、獲取類方法的注解屬性

@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface TestMethod{
    String method();
}

class Test{
    @TestMethod(method = "我是方法")
    public void test(){

    }
}
//通過反射獲取方法注解
Class<Test> testClass2 = Test.class;
try {
    Method method = testClass2.getDeclaredMethod("test");
    if(method.isAnnotationPresent(TestMethod.class)){
        TestMethod methodAnnotation = method.getDeclaredAnnotation(TestMethod.class);
        System.out.println(methodAnnotation.method());
    }
} catch (Exception e) {
    e.printStackTrace();
}
//打印結(jié)果
我是方法
JDK 提供的內(nèi)置注解

JDK 給我們提供了很多內(nèi)置的注解搀捷,其中常用的有:
1、@Override
2多望、@Deprecated
3嫩舟、@SuppressWarnings
4、@FunctionalInterface

@Override

@Override 用在方法上便斥,表示這個方法重寫了父類的方法至壤,例如toString 方法

@Override
public String toString() {
    return super.toString();
}
@Deprecated
廢棄方法示例.png

可以看到用 @Deprecated 注解的方法調(diào)用的時候會被劃掉

@SuppressWarnings

@SuppressWarnings 用于忽略警告信息威始,常見的取值如下:
deprecation:使用了不贊成使用的類或方法時的警告(使用@Deprecated使得編譯器產(chǎn)生的警告)
unchecked:執(zhí)行了未檢查的轉(zhuǎn)換時的警告枢纠,例如當(dāng)使用集合時沒有用泛型 (Generics) 來指定集合保存的類型; 關(guān)閉編譯器警告
fallthrough:當(dāng) Switch 程序塊直接通往下一種情況而沒有 Break 時的警告
path:在類路徑、源文件路徑等中有不存在的路徑時的警告
serial:當(dāng)在可序列化的類上缺少 serialVersionUID 定義時的警告
finally:任何 finally 子句不能正常完成時的警告
rawtypes: 泛型類型未指明
unused:引用定義了黎棠,但是沒有被使用
all:關(guān)于以上所有情況的警告

以泛型舉個例子:


示例.png

當(dāng)我們創(chuàng)建 List 未指定泛型時晋渺,編譯器就會報黃提示我們未指明泛型,這個時候就可以使用這個注解了:


示例.png
@FunctionalInterface

@FunctionalInterfaceJDK 1.8 新增的注解脓斩,用于約定函數(shù)式接口木西,函數(shù)式接口就是接口中只有一個抽象方法

@FunctionalInterface
interface testInterface{
    void testMethod();
}
示例.png
注解實際應(yīng)用場景
使用自定義注解代替枚舉類型

主要針對源碼階段注解
這個在我們實際工作中也挺常用的,使用枚舉類型開銷大随静,我們一般都會使用自定義注解進(jìn)行替代八千,如下:

//1吗讶、使用枚舉
enum EnumFontType{
    ROBOTO_REGULAR,ROBOTO_MEDIUM,ROBOTO_BOLD
}
//實際調(diào)用
EnumFontType type1 = EnumFontType.ROBOTO_BOLD;

//================================ 完美的分割線 ==================================
//2、使用自定義注解
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
@IntDef({AnnotationFontType.ROBOTO_REGULAR,AnnotationFontType.ROBOTO_MEDIUM,AnnotationFontType.ROBOTO_BOLD})
@interface AnnotationFontType{
    int ROBOTO_REGULAR = 1;
    int ROBOTO_MEDIUM = 2;
    int ROBOTO_BOLD = 3;
}
//實際調(diào)用
@AnnotationFontType int type2 = AnnotationFontType.ROBOTO_MEDIUM;
注解處理器 (APT)

主要針對編譯階段注解
實際我們?nèi)粘i_發(fā)中恋捆,經(jīng)常會遇到它照皆,因為我們常用的一些開源庫如 ButterKnifeRetrofit沸停,Arouter膜毁,EventBus 等等都使用到了APT 技術(shù)。也正是因為這些著名的開源庫愤钾,才使得 APT技術(shù)越來越火瘟滨,在本系列的下一篇中,我也會講到能颁。

運(yùn)行時注解處理

主要針對運(yùn)行階段注解

舉個實際的例子:例如我們開車去自助加油機(jī)加油杂瘸,設(shè)定的Money200,如果少于 200則提示 加油中...伙菊,否則提示 油已加滿胧沫,如果出現(xiàn)異常情況,提示 加油失敗

現(xiàn)在我們通過注解來實現(xiàn)一下它占业,如下:

@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface OilAnnotation{
    double maxOilMoney() default 0;
}

class GasStation{

    @OilAnnotation(maxOilMoney = 200)
    public void addOil(double money){
        String tips = processOilAnnotation(money);
        System.out.println(tips);
    }

    @SuppressWarnings("all")
    private String processOilAnnotation(double money){
        try {
            Class<GasStation> aClass = GasStation.class;
            //獲取當(dāng)前方法的注解
            Method addOilMethod = aClass.getDeclaredMethod("addOil", double.class);
            //獲取方法注解是否存在
            boolean annotationPresent = addOilMethod.isAnnotationPresent(OilAnnotation.class);
            if(annotationPresent){
                OilAnnotation oilAnnotation = addOilMethod.getDeclaredAnnotation(OilAnnotation.class);
                if(money >= oilAnnotation.maxOilMoney()){
                    return "油已加滿";
                }else {
                    return "加油中...";
                }
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return "加油失敗";
    }
}

new GasStation().addOil(100);
//打印結(jié)果
加油中...
  
new GasStation().addOil(200);
//打印結(jié)果
油已加滿
總結(jié)

本篇文章講的一些重點內(nèi)容:
1绒怨、自定義注解時,元注解的組合使用
2谦疾、注解屬性的定義南蹂,使用和獲取
3、一些常用的 JDK 內(nèi)置注解
4念恍、注解的實際應(yīng)用及運(yùn)行階段注解的一個實踐

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末六剥,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子峰伙,更是在濱河造成了極大的恐慌疗疟,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瞳氓,死亡現(xiàn)場離奇詭異策彤,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)匣摘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門店诗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人音榜,你說我怎么就攤上這事庞瘸。” “怎么了赠叼?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵擦囊,是天一觀的道長违霞。 經(jīng)常有香客問我,道長瞬场,這世上最難降的妖魔是什么葛家? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮泌类,結(jié)果婚禮上癞谒,老公的妹妹穿的比我還像新娘。我一直安慰自己刃榨,他們只是感情好弹砚,可當(dāng)我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著枢希,像睡著了一般桌吃。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上苞轿,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天茅诱,我揣著相機(jī)與錄音,去河邊找鬼搬卒。 笑死瑟俭,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的契邀。 我是一名探鬼主播摆寄,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼坯门!你這毒婦竟也來了微饥?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤古戴,失蹤者是張志新(化名)和其女友劉穎欠橘,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體现恼,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡肃续,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了述暂。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片痹升。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡建炫,死狀恐怖畦韭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情肛跌,我是刑警寧澤艺配,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布察郁,位于F島的核電站,受9級特大地震影響转唉,放射性物質(zhì)發(fā)生泄漏皮钠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一赠法、第九天 我趴在偏房一處隱蔽的房頂上張望麦轰。 院中可真熱鬧,春花似錦砖织、人聲如沸劝堪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至含末,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間侵佃,已是汗流浹背勃黍。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留娜氏,地道東北人拳缠。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像贸弥,于是被迫代替她去往敵國和親脊凰。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,700評論 2 354

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