初談一Java Annotation

由于年前各種原因,博客一直沒(méi)法更新。在這里我表示十分的歉意,希望各位能夠繼續(xù)關(guān)注我的博客。我也將跟大家一起加油,努力!

我相信 Java 注解大家都不會(huì)陌生,許多開(kāi)源的第三方框架中都有它的身影如:butterknife,eventbus,retrofit2, dagger2 等等...

有這樣一個(gè)細(xì)節(jié)你是否注意到了呢?

在 Activity 中重寫(xiě) onCreate 方法:

    @Override  //標(biāo)記注解
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

@Override 這里就用到了非常常見(jiàn)的 標(biāo)記注解,我們繼續(xù)看看 Override 注解:

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

@Retention ,@Target 又分別有什么含義呢?請(qǐng)繼續(xù)往后面看。

什么是注解

注解(Annotation),也叫(metadata)元數(shù)據(jù)。一種代碼級(jí)別的說(shuō)明。它是JDK1.5及以后版本引入的一個(gè)特性,與類(lèi)、接口、枚舉是在同一個(gè)層次。它可以聲明在包、類(lèi)、字段、方法、局部變量、方法參數(shù)等的前面,用來(lái)對(duì)這些元素進(jìn)行說(shuō)明,注釋。這些信息被存儲(chǔ)在Annotation的“name=value”結(jié)構(gòu)對(duì)中。

那么你可能又有疑問(wèn)什么是(metadata)元數(shù)據(jù)呢?

這個(gè)解釋起來(lái)比較抽象,我的理解是:“數(shù)據(jù)的數(shù)據(jù)”。

那它有什么作用呢,如果要對(duì)于元數(shù)據(jù)的作用進(jìn)行分類(lèi),還沒(méi)有明確的定義,不過(guò)我們可以根據(jù)它所起的作用,大致可分為三類(lèi):

  • 編寫(xiě)文檔:通過(guò)代碼里標(biāo)識(shí)的元數(shù)據(jù)生成文檔。

  • 代碼分析:通過(guò)代碼里標(biāo)識(shí)的元數(shù)據(jù)對(duì)代碼進(jìn)行分析。

  • 編譯檢查:通過(guò)代碼里標(biāo)識(shí)的元數(shù)據(jù)讓編譯器能實(shí)現(xiàn)基本的編譯檢查

注解的分類(lèi)

根據(jù)注解的參數(shù)個(gè)數(shù)分類(lèi):

  • 標(biāo)記注解。一個(gè)沒(méi)有成員的 Annotation,這種類(lèi)型僅僅使用自身的存在與否來(lái)為我們提供信息。標(biāo)記注解非常常見(jiàn),比如上面所說(shuō)的 @Override

  • 單值注解。成員的參數(shù)為單個(gè)參數(shù)

  • 完整注解。成員的參數(shù)為多個(gè)參數(shù)

根據(jù)注解使用的方法和用途分類(lèi):

  • JDK內(nèi)置系統(tǒng)注解

  • 元注解

  • 自定義注解

它們都不會(huì)直接影響到程序的語(yǔ)義,只是作為注解(標(biāo)識(shí))存在,我們可以通過(guò)反射機(jī)制編程實(shí)現(xiàn)對(duì)這些元數(shù)據(jù)(用來(lái)描述數(shù)據(jù)的數(shù)據(jù))的訪問(wèn)。另外,你可以在編譯時(shí)選擇代碼里的注解是否只存在于源代碼級(jí),或者它也能在class文件、或者運(yùn)行時(shí)中出現(xiàn)(SOURCE/CLASS/RUNTIME)。下面我具體根據(jù)注解使用的方法和用途分類(lèi)來(lái)講解。

內(nèi)置注解

內(nèi)置注解分為三類(lèi):

  • @Override

  • @Deprecated

  • @SuppressWarnings

1、@Override

它的作用是對(duì)覆蓋超類(lèi)中方法的方法進(jìn)行標(biāo)記,如果被標(biāo)記的方法并沒(méi)有實(shí)際覆蓋超類(lèi)中的方法,則編譯器會(huì)發(fā)出錯(cuò)誤警告。換句話(huà)理解就是重寫(xiě)父類(lèi)方法,方法前的標(biāo)記。

    @Override
    protected void onStart() {
        super.onStart();
    }
2、@Deprecated

它的作用是對(duì)不應(yīng)該再使用的方法添加注解,當(dāng)編程人員使用這些方法時(shí),將會(huì)在編譯時(shí)顯示提示信息,它與javadoc里的@deprecated標(biāo)記有相同的功能。

public class User {

    @Deprecated
    public static String getName(){
        return "user";
    }

}

調(diào)用 getName 方法:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //@Deprecated 方法已經(jīng)過(guò)時(shí) 出現(xiàn)中劃線
        User.getName();

    }

}
3、@SuppressWarnings

其參數(shù)有:

  • deprecation 使用了過(guò)時(shí)的類(lèi)或方法時(shí)的警告
  • unchecked 執(zhí)行了未檢查的轉(zhuǎn)換時(shí)的警告
  • fallthrough 當(dāng) switch 程序塊直接通往下一種情況而沒(méi)有 break 時(shí)的警告
  • path 在類(lèi)路徑、源文件路徑等中有不存在的路徑時(shí)的警告
  • serial 當(dāng)在可序列化的類(lèi)上缺少serialVersionUID 定義時(shí)的警告
  • finally 任何 finally 子句不能正常完成時(shí)的警告
  • all 關(guān)于以上所有情況的警告
public class User {
    
    @SuppressWarnings("deprecation")
    public static int getAge() {
        return 18;
    }

}

元注解

元注解就是定義注解的注解,由 java API 提供,分別有四個(gè)元注解:

  • @Target

  • @Retention

  • @Documented

  • @Inherited

1、@Target

用于描述注解的使用范圍。修飾的對(duì)象范圍:packages、types(類(lèi)、接口、枚舉、Annotation類(lèi)型)、類(lèi)型成員(方法、構(gòu)造方法、成員變量、枚舉值)、方法參數(shù)和本地變量(如 catch 參數(shù))。

它的值在枚舉類(lèi) ElemenetType 中:

  • CONSTRUCTOR: 用于描述構(gòu)造器
  • FIELD : 用于描述域
  • LOCAL_VARIABLE: 用于描述局部變量
  • METHOD : 用于描述方法
  • PACKAGE : 用于描述包
  • PARAMETER : 用于描述參數(shù)
  • TYPE : 用于描述類(lèi)、接口(包括注解類(lèi)型) 或enum聲明

Mode類(lèi)可以注解類(lèi)的成員變量:

@Target(ElementType.FIELD)
@Documented
public @interface Mode {
    public int value() default 0;
}

Person可以注解類(lèi)、接口(包括注解類(lèi)型)、或者enum聲明:

@Target(ElementType.TYPE)
public @interface People {
    public String value() default "";
}
2、@Retention

表示需要在什么級(jí)別保存該注釋信息,用于描述注解的生命周期(即:被描述的注解在什么范圍內(nèi)有效),定義了該Annotation被保留的時(shí)間長(zhǎng)短:某些Annotation僅出現(xiàn)在源代碼中,而被編譯器丟棄;而另一些卻被編譯在class文件中;編譯在class文件中的Annotation可能會(huì)被虛擬機(jī)忽略,而另一些在class被裝載時(shí)將被讀取。

RetentionPoicy取值:

  • SOURCE : 在源文件中有效(即源文件保留)
  • CLASS : 在class文件中有效(即class保留)
  • RUNTIME : 在運(yùn)行時(shí)有效(即運(yùn)行時(shí)保留)

Page 注解的RetentionPolicy 的值為 RUNTIME,這樣注解處理器可以通過(guò)反射,獲取到該注解的屬性,從而做一些運(yùn)行時(shí)的邏輯處理。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Page {
    public int value() default 1;
}
3、@Documented

用于描述其它類(lèi)型的annotation應(yīng)該被作為被標(biāo)注的程序成員的公共API,Documented 是一個(gè)標(biāo)記注解,沒(méi)有成員。將此注解包含在 javadoc 中 ,它代表著此注解會(huì)被javadoc工具提取成文檔。在doc文檔中的內(nèi)容會(huì)因?yàn)榇俗⒔獾男畔?nèi)容不同而不同。

@Documented
public @interface Page {
    public int value() default 0;
}
4、@Inherited

允許子類(lèi)繼承父類(lèi)中的注解,是一個(gè)標(biāo)記注解,@Inherited闡述了某個(gè)被標(biāo)注的類(lèi)型是被繼承的。如果一個(gè)使用了@Inherited修飾的annotation類(lèi)型被用于一個(gè)class,則這個(gè)annotation將被用于該class的子類(lèi)。

@Inherited annotation類(lèi)型是被標(biāo)注過(guò)的class的子類(lèi)所繼承。類(lèi)并不從它所實(shí)現(xiàn)的接口繼承annotation,方法并不從它所重載的方法繼承annotation

當(dāng)@Inherited annotation類(lèi)型標(biāo)注的annotation的Retention是RetentionPolicy.RUNTIME,則反射API增強(qiáng)了這種繼承性。如果我們使用java.lang.reflect去查詢(xún)一個(gè)@Inherited annotation類(lèi)型的annotation時(shí),反射代碼檢查將展開(kāi)工作:檢查class和其父類(lèi),直到發(fā)現(xiàn)指定的annotation類(lèi)型被發(fā)現(xiàn),或者到達(dá)類(lèi)繼承結(jié)構(gòu)的頂層。

自定義注解

使用@interface自定義注解時(shí),自動(dòng)繼承了java.lang.annotation.Annotation接口,由編譯程序自動(dòng)完成其他細(xì)節(jié)。在定義注解時(shí),不能繼承其他的注解或接口。@interface用來(lái)聲明一個(gè)注解,其中的每一個(gè)方法實(shí)際上是聲明了一個(gè)配置參數(shù)。方法的名稱(chēng)就是參數(shù)的名稱(chēng),返回值類(lèi)型就是參數(shù)的類(lèi)型(返回值類(lèi)型只能是基本類(lèi)型、Class、String、enum)??梢酝ㄟ^(guò)default來(lái)聲明參數(shù)的默認(rèn)值。以上所有例子都屬于自定義注解。自定義注解具有以下固定格式:

 public @interface 注解名{注解體}
  • 所有基本數(shù)據(jù)類(lèi)型(int,float,boolean,byte,double,char,long,short)
  • String類(lèi)型
  • Class類(lèi)型
  • enum類(lèi)型
  • Annotation類(lèi)型
  • 以上所有類(lèi)型的數(shù)組

注意:只能有public或默認(rèn)(default)這兩個(gè)訪問(wèn)權(quán)修飾,參數(shù)成員只能用以上6種類(lèi)型,如果只有一個(gè)參數(shù)成員,最好把參數(shù)名稱(chēng)設(shè)為"value"。

Shade 形狀注解:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Shade {

    public enum ShadeType {
        Triangle("三角"),
        Four("四邊"),
        Five("五角");

        private String type;

        ShadeType(String type) {
            this.type = type;
        }

        @Override
        public String toString() {
            return type;
        }
    }

    public ShadeType shader() default ShadeType.Triangle;

}

讀取注解

這里我們將使用反射去讀取注解。Java在java.lang.reflect 包下新增了AnnotatedElement接口,該接口代表程序中可以接受注解的程序元素。該接口主要有如下幾個(gè)方法:

default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)

判斷該程序元素上是否包含指定類(lèi)型的注解,存在則返回true,否則返回false。

<T extends Annotation> T getAnnotation(Class<T> var1);

返回該程序元素上存在的、指定類(lèi)型的注解,如果該類(lèi)型注解不存在,則返回null。

Annotation[] getAnnotations();

返回該程序元素上存在的所有注解。

 Annotation[] getDeclaredAnnotations();

返回直接存在于此元素上的所有注釋。與此接口中的其他方法不同,該方法將忽略繼承的注釋。(如果沒(méi)有注釋直接存在于此元素上,則返回長(zhǎng)度為零的一個(gè)數(shù)組。)該方法的調(diào)用者可以隨意修改返回的數(shù)組;這不會(huì)對(duì)其他調(diào)用者返回的數(shù)組產(chǎn)生任何影響。

    public static void getInfo(Class<?> clazz) {
        // 獲取該類(lèi)所有聲明的方法
        Field[] fields = clazz.getDeclaredFields();

        if (fields == null) return;

        for (Field field : fields) {

            if (field.isAnnotationPresent(Mode.class)) {
                Mode m0 = field.getAnnotation(Mode.class);
                System.out.println("****name=" + m0.value());
            }

            if (field.isAnnotationPresent(Shade.class)) {
                Shade s0 = field.getAnnotation(Shade.class);
                System.out.println("****shade=" + s0.shader().toString());
            }

            if (field.isAnnotationPresent(People.class)) {
                People p0 = field.getAnnotation(People.class);
                System.out.println("****name=" + p0.Name() + "**age=" + p0.Age() + "**price=" + p0.Price());
            }
        }

    }

使用自定義注解:

public class User {

    @Mode(value = "小石頭")
    public String name;

    @Shade(shader = Shade.ShadeType.Five)
    public String shape;

    @People(Age = 18, Price = 100f, Name = "小寶")
    public int profile;

}

運(yùn)行:

       getInfo(User.class);

打印:

****name=小石頭
****shade=五角
****name=小寶**age=18**price=100.0

注意:使用反射去讀取注解,必須將Retention的值選為Runtime。

源碼傳送門(mén)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容