注解淺析

注解(Annotation)是JDK1.5開(kāi)始引入的抛姑,與類(lèi)谓传、接口、枚舉是在同一個(gè)層次聘殖,雖然在平時(shí)開(kāi)發(fā)中我們很少直接和它打交道晨雳,但是我們卻經(jīng)常用到它行瑞,例如 Java 本身已經(jīng)為我們提供了幾個(gè)常用注解:@Deprecated@Override餐禁、@SuppressWarnings等蘑辑,熟悉吧!除此之外坠宴,Android 中有名的ButterKnife洋魂、EventBusDagger2喜鼓、Retrofit都用到了注解副砍,所以注解的重要性可見(jiàn)一斑!

一庄岖、注解的主要作用

  • 代碼格式檢查豁翎,方便IDE檢查出錯(cuò)誤代碼,例如資源類(lèi)型注解
  • 減少重復(fù)的工作隅忿,例如ButterKnife心剥,我們也可以用注解做類(lèi)似的事情,提高開(kāi)發(fā)效率
  • 信息配置背桐,運(yùn)行時(shí)動(dòng)態(tài)處理优烧,獲取信息,可實(shí)現(xiàn)類(lèi)似配置文件的功能

二链峭、注解的定義

如何定義一個(gè)注解呢畦娄?可以先看看這些已有的注解是如何實(shí)現(xiàn)的,我們先以@Override為例:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

可以看到注解是通過(guò)@interface關(guān)鍵字來(lái)定義的弊仪,和接口的定義類(lèi)似熙卡,但是又多了@Target()@Retention()励饵,這些是java中的元注解驳癌,元注解可以理解為內(nèi)置的基礎(chǔ)注解,用來(lái)限定役听、說(shuō)明自定義注解颓鲜。除了這兩個(gè)元注解外,還有三個(gè)元注解@Inherited禾嫉、@Repeatable灾杰、@Documented,后邊會(huì)詳細(xì)解釋這些元注解的作用熙参!

再看看@SuppressWarnings注解的實(shí)現(xiàn):

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

@Override相比最大的區(qū)別就是注解體中多了String[] value();艳吠,它代表注解的屬性!關(guān)于注解屬性也會(huì)在后邊詳細(xì)的介紹孽椰!

所以定義注解時(shí)昭娩,除了使用@interface還需要考慮元注解凛篙、注解屬性,一個(gè)自定義注解的偽碼如下:

@元注解0
@元注解1
@元注解2
public @interface 注解名稱(chēng) {
    類(lèi)型 attr0();
    類(lèi)型 attr1();
}

三栏渺、元注解

@Retention

代表注解的保留策略呛梆,即存活時(shí)間】恼铮可選的策略如下:

  • RetentionPolicy.SOURCE 注解只保留在源碼填物,在編譯器進(jìn)行編譯時(shí)會(huì)被忽略
  • RetentionPolicy.CLASS 注解由編譯器保存在class文件中,但不需要在運(yùn)行時(shí)由VM保留霎终,無(wú)法通過(guò)反射讀取滞磺,這是默認(rèn)的策略。
  • RetentionPolicy.RUNTIME 注解由編譯器保存在class文件中莱褒,并在運(yùn)行時(shí)由VM保留击困,可以通過(guò)反射讀取。

@Target

代表注解可能出現(xiàn)的語(yǔ)法位置广凸,即可以在哪里使用定義的注解阅茶,可選的位置如下:

  • ElementType.TYPE 類(lèi)、接口(包括注解類(lèi)型)或枚舉聲明
  • ElementType.FIELD 字段聲明
  • ElementType.METHOD 方法聲明
  • ElementType.PARAMETER 方法的參數(shù)聲明
  • ElementType.CONSTRUCTOR 類(lèi)的構(gòu)造法聲明
  • ElementType.LOCAL_VARIABLE 局部變量聲明
  • ElementType.ANNOTATION_TYPE 注解聲明
  • ElementType.PACKAGE 包聲明
  • ElementType.TYPE_PARAMETER JDK1.8新加的谅海,類(lèi)型參數(shù)聲明
  • ElementType.TYPE_USE JDK1.8新加的脸哀,類(lèi)型使用聲明

通過(guò)查看注解的@Target元注解,可以知道注解能在哪些地方使用胁赢!

@Inherited

表明注解類(lèi)型是自動(dòng)繼承的企蹭,怎么理解呢白筹?先看代碼:

@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface InheritedTest {
}
@InheritedTest
public class P {
}
public class P1 extends P {
}

我們自定義了一個(gè)被@Inherited注解的InheritedTest注解智末,同時(shí)用自定義的注解來(lái)注解P類(lèi),如果P類(lèi)有一個(gè)子類(lèi)P1徒河,則該子類(lèi)也繼承了父類(lèi)的InheritedTest注解系馆,有點(diǎn)繞......

@Repeatable

JDK1.8新加的,表明當(dāng)自定義注解的屬性值有多個(gè)時(shí)顽照,自定義注解可以多次使用由蘑,舉個(gè)例子,手機(jī)可以有打電話代兵、上網(wǎng)尼酿、相機(jī)等功能,可把這些功能看做一個(gè)個(gè)注解植影,并給手機(jī)類(lèi)使用裳擎,使手機(jī)類(lèi)具有對(duì)應(yīng)功能,具體看代碼:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Funcs {
    Func[] value();
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Funcs.class)
public @interface Func {
    String name() default "";
}
@Func(name = "CALL")
@Func(name = "INTERNET")
@Func(name = "CAMERA")
public class XPhone {
}

我們定義注解Func使用了@Repeatable(Funcs.class)思币,其中Funcs也是我們自定義的注解:

public @interface Funcs {
    Func[] value();
}

其中有一個(gè)數(shù)組類(lèi)型的 value屬性鹿响,而且名稱(chēng)必須為value羡微,關(guān)于注解的屬性后邊會(huì)說(shuō)到。由于@Func可以重復(fù)使用惶我,@Funcs就相當(dāng)于接收重復(fù)注解的容器妈倔。

如果在AS中無(wú)法多次使用@Func,請(qǐng)確認(rèn)是否配置了JDK1.8:

android {
    ......
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

@Documented

這個(gè)相對(duì)簡(jiǎn)單绸贡,說(shuō)明該注解將被包含在javadoc中盯蝴。

四、注解的屬性

在注解中定義屬性和在接口中定義方法的格式類(lèi)似听怕,例如:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    String name();
    int age() default 18;
    String[] favour();
}

這樣就給Test注解定義了name结洼、age兩個(gè)屬性,并用default關(guān)鍵字指定age的默認(rèn)值為18叉跛∷扇蹋可以這樣使用定義好的注解:

@TestAnnotation(name = "Tom", age = 12, favour = {"music", "sports"})
public class Test {
}

由于age有默認(rèn)值,可以在使用注解時(shí)不指定它的值筷厘。由于favour的類(lèi)型為數(shù)組鸣峭,所以當(dāng)其有多個(gè)值時(shí)需要用{}包起來(lái)。
如果自定義注解沒(méi)有屬性或者屬性有默認(rèn)值酥艳,則使用時(shí)可以直接寫(xiě)@TestAnnotation摊溶,省略后邊的括號(hào)。

注解的屬性支持的數(shù)據(jù)類(lèi)型如下:

  • 基本類(lèi)型(byte充石、short莫换、int、float骤铃、double拉岁、long、char惰爬、boolean)喊暖,不包括其對(duì)應(yīng)的包裝類(lèi)型
  • String
  • Class,即Class<?>
  • enum撕瞧,例如enum staus {A, B, C}
  • 注解陵叽,例如Override test();
  • 上述類(lèi)型對(duì)應(yīng)的數(shù)組

注解相關(guān)的語(yǔ)法糖就介紹到這里了,接下來(lái)要關(guān)注的是當(dāng)一個(gè)類(lèi)丛版、方法巩掺、屬性等使用了注解后,如何提取注解上的信息页畦。

五胖替、注解與反射

要提取注解上的信息,就要用到反射相關(guān)的知識(shí)了,下面看一個(gè)完整的例子刊殉,首先定義TestAnnotation注解殉摔,可以作用在類(lèi)、字段记焊、方法聲明的地方逸月,并可以在運(yùn)行時(shí)被獲取,以及三個(gè)屬性:

@Target(value = {ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    String name();
    int age() default 18;
    String[] favour();
}

Test的類(lèi)遍膜、字段碗硬、方法聲明的地方分別使用TestAnnotation注解:

@TestAnnotation(name = "Test", age = 20, favour = {"music", "sports"})
public class Test {

    @TestAnnotation(name = "testField", favour = {"reading", "sports"})
    private int testField;

    @TestAnnotation(name = "testMethod", age = 10, favour = {"dancing", "music"})
    public void testMethod() {

    }

    @TestAnnotation(name = "testMethod1", age = 12, favour = {"music"})
    public void testMethod1() {

    }
}

通過(guò)反射的方式提取注解信息:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        resolve();
    }

    private void resolve() {
        // 解析類(lèi)上的注解
        boolean isPresent = Test.class.isAnnotationPresent(TestAnnotation.class);
        if (isPresent) {
            TestAnnotation annotation = Test.class.getAnnotation(TestAnnotation.class);
            showAnnotation(annotation);
        }
        // 解析字段上的注解
        Field[] fields = Test.class.getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(TestAnnotation.class)) {
                TestAnnotation annotation = field.getAnnotation(TestAnnotation.class);
                showAnnotation(annotation);
            }
        }
        // 解析方法上的注解
        Method[] methods = Test.class.getDeclaredMethods();
        for (Method method : methods) {
            if (method.isAnnotationPresent(TestAnnotation.class)) {
                TestAnnotation annotation = method.getAnnotation(TestAnnotation.class);
                showAnnotation(annotation);
            }
        }
    }

    private void showAnnotation(TestAnnotation annotation) {
        Log.e("Annotation", annotation.name() + "#" + annotation.age() + "#" + Arrays.toString(annotation.favour()));
    }
}

運(yùn)行后的效果如下:


0

其中涉及到了幾個(gè)關(guān)鍵的方法,Class瓢颅、Method恩尾、Field等類(lèi)都有這樣的方法:

  • boolean isAnnotationPresent(Class<? extends Annotation> annotation),用來(lái)判斷是否使用了某個(gè)注解挽懦。
  • public <A extends Annotation> A getAnnotation(Class<A> annotation)翰意,獲得指定名稱(chēng)的注解對(duì)象。
  • public Annotation[] getAnnotations()信柿,返回對(duì)應(yīng)元素的全部注解冀偶。
  • public Annotation[] getDeclaredAnnotations(),返回直接在對(duì)應(yīng)元素上使用的注解渔嚷,不包括父類(lèi)的注解进鸠。

六、枚舉的替代方案

由于性能形病、開(kāi)銷(xiāo)的問(wèn)題客年,在Android中不建議直接使用枚舉,要實(shí)現(xiàn)類(lèi)似枚舉的功能可以使用android.support.annotation包提供的@IntDef漠吻、@StringDef注解可以解決這個(gè)問(wèn)題量瓜。
例如要表示一個(gè)Button在界面的位置,使用枚舉可以這樣定義:

public enum Position {
    LEFT, TOP, RIGHT, BOTTOM
}

如果使用@IntDef注解侥猩,則這樣實(shí)現(xiàn):

@IntDef({LEFT, TOP, RIGHT, BOTTOM})
@Retention(RetentionPolicy.SOURCE)
public @interface Position {
    int LEFT = 0;
    int TOP = 1;
    int RIGHT = 2;
    int BOTTOM = 3;
}

如果使用@StringDef實(shí)現(xiàn)也類(lèi)似榔至,只是對(duì)應(yīng)的位置常量為String類(lèi)型。

看一個(gè)簡(jiǎn)單的使用例子欺劳,在自定義View中提供一個(gè)setButtonPosition()方法,其參數(shù)使用了@Position注解:

public class MyView extends RelativeLayout {
    @IntDef({LEFT, TOP, RIGHT, BOTTOM})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Position {
        int LEFT = 0;
        int TOP = 1;
        int RIGHT = 2;
        int BOTTOM = 3;
    }

    public MyView(Context context) {
        this(context, null);
    }

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    // 設(shè)置Button位置
    public void setButtonPosition(@Position int pos) {
        switch (pos) {
            case Position.BOTTOM:
                break;
            case Position.LEFT:
                break;
            case Position.RIGHT:
                break;
            case Position.TOP:
                break;
        }
    }
}

當(dāng)調(diào)用該方法時(shí)可以保證傳入的參數(shù)是類(lèi)型安全的铅鲤,即只能傳入注解中定義的常量:

new MyView(MainActivity.this).setButtonPosition(MyView.Position.BOTTOM);

因?yàn)?code>setButtonPosition()方法的參數(shù)使用了@Position注解划提,在AS中可以方便的完善switch-case語(yǔ)句:

1

七、常用的注解

首先在Android中邢享,android.support.annotation包中提供了許多好用的注解鹏往,以下列舉部分:

  • 資源類(lèi)型注解,用來(lái)標(biāo)注元素必須為指定的資源類(lèi)型骇塘,例如@AnimRes伊履、@ColorRes韩容、@IdRes@LayoutRes唐瀑、@LayoutRes群凶、@StringRes
  • 顏色值注解@ColorInt哄辣,代表ARGB顏色值请梢,而不是顏色的資源id,注意和@ColorRes區(qū)分
  • 空值注解力穗,@Nullable:可以為空毅弧、@NonNull:不能為空
  • 類(lèi)型定義注解@IntDef当窗、@StringDef
  • 值約束注解够坐,即約束元素取值范圍,@Size崖面、@IntRange咆霜、@FloatRange,例如有如下方法嘶朱,可以保證設(shè)置的month值為[1, 12]
public void setMonth(@IntRange(from = 1, to = 12) int month){
}
  • 權(quán)限注解蛾坯,@RequiresPermission,即檢查某操作是否有必要的權(quán)限疏遏,例如:
@RequiresPermission(Manifest.permission.CAMERA)
public void takePhoto() {
}
  • 線程注解脉课,可以標(biāo)注方法、構(gòu)造函數(shù)财异、類(lèi)倘零、接口、枚舉只能在指定的線程被調(diào)用戳寸,例如@MainThread呈驶、@UiThread@WorkerThread
  • 重寫(xiě)方法注解疫鹊,@CallSuper袖瞻,當(dāng)子類(lèi)重寫(xiě)父類(lèi)方法時(shí),要保證父方法也必須被調(diào)用拆吆,則對(duì)應(yīng)父方法要使用該注解

Java也為我們提供了一些常用的注解:

  • @Override聋迎,代表某方法是重寫(xiě)父類(lèi)中的方法
  • @Deprecated,表示被標(biāo)記的內(nèi)容已經(jīng)過(guò)時(shí)枣耀、不建議被使用霉晕,如果被使用編譯器會(huì)報(bào)警告,但程序也能正常運(yùn)行。
  • @SuppressWarnings牺堰,由于內(nèi)容被@Deprecated標(biāo)記后拄轻,編譯器會(huì)有警告,如果想忽略警告可以使用@SuppressWarnings
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末伟葫,一起剝皮案震驚了整個(gè)濱河市恨搓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌扒俯,老刑警劉巖奶卓,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異撼玄,居然都是意外死亡夺姑,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén)掌猛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)盏浙,“玉大人,你說(shuō)我怎么就攤上這事荔茬》媳欤” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵慕蔚,是天一觀的道長(zhǎng)丐黄。 經(jīng)常有香客問(wèn)我,道長(zhǎng)孔飒,這世上最難降的妖魔是什么灌闺? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮坏瞄,結(jié)果婚禮上桂对,老公的妹妹穿的比我還像新娘。我一直安慰自己鸠匀,他們只是感情好蕉斜,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著缀棍,像睡著了一般宅此。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上睦柴,一...
    開(kāi)封第一講書(shū)人閱讀 49,784評(píng)論 1 290
  • 那天诽凌,我揣著相機(jī)與錄音,去河邊找鬼坦敌。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的狱窘。 我是一名探鬼主播杜顺,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蘸炸!你這毒婦竟也來(lái)了躬络?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤搭儒,失蹤者是張志新(化名)和其女友劉穎穷当,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體淹禾,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡馁菜,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了铃岔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片汪疮。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖毁习,靈堂內(nèi)的尸體忽然破棺而出智嚷,到底是詐尸還是另有隱情,我是刑警寧澤纺且,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布盏道,位于F島的核電站,受9級(jí)特大地震影響载碌,放射性物質(zhì)發(fā)生泄漏猜嘱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一恐仑、第九天 我趴在偏房一處隱蔽的房頂上張望泉坐。 院中可真熱鬧,春花似錦裳仆、人聲如沸腕让。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)纯丸。三九已至,卻和暖如春静袖,著一層夾襖步出監(jiān)牢的瞬間觉鼻,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工队橙, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留坠陈,地道東北人萨惑。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像仇矾,于是被迫代替她去往敵國(guó)和親庸蔼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

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