簡(jiǎn)單的Java注解機(jī)制

一、什么是注解

???????注解也叫元數(shù)據(jù),例如我們常見的@Override和@Deprecated,注解是JDK1.5版本開始引入的一個(gè)特性,用于對(duì)代碼進(jìn)行說明,可以對(duì)包、類、接口、字段、方法參數(shù)、局部變量等進(jìn)行注解

一般常用的注解可以分為三類:

  • 一類是Java自帶的標(biāo)準(zhǔn)注解,包括@Override(標(biāo)明重寫某個(gè)方法)、@Deprecated(標(biāo)明某個(gè)類或方法過時(shí))和@SuppressWarnings(標(biāo)明要忽略的警告),使用這些注解后編譯器就會(huì)進(jìn)行檢查
  • 一類為元注解,元注解是用于定義注解的注解,包括@Retention(標(biāo)明注解被保留的階段)、@Target(標(biāo)明注解使用的范圍)、@Inherited(標(biāo)明注解可繼承)、@Documented(標(biāo)明是否生成javadoc文檔)
  • 一類為自定義注解,可以根據(jù)自己的需求定義注解

二、注解的用途

  • 生成文檔,通過代碼里標(biāo)識(shí)的元數(shù)據(jù)生成javadoc文檔。
  • 編譯檢查,通過代碼里標(biāo)識(shí)的元數(shù)據(jù)讓編譯器在編譯期間進(jìn)行檢查驗(yàn)證。
  • 編譯時(shí)動(dòng)態(tài)處理,編譯時(shí)通過代碼里標(biāo)識(shí)的元數(shù)據(jù)動(dòng)態(tài)處理,例如動(dòng)態(tài)生成代碼。
  • 運(yùn)行時(shí)動(dòng)態(tài)處理,運(yùn)行時(shí)通過代碼里標(biāo)識(shí)的元數(shù)據(jù)動(dòng)態(tài)處理,例如使用反射注入實(shí)例

三、注解的使用

1、Java自帶的標(biāo)準(zhǔn)注解

常見標(biāo)準(zhǔn)的Annotation:

1.)Override
??????? java.lang.Override是一個(gè)標(biāo)記類型注解,它被用作標(biāo)注方法。它說明了被標(biāo)注的方法重載了父類的方法,起到了斷言的作用。如果我們使用了這種注解在一個(gè)沒有覆蓋父類方法的方法時(shí),java編譯器將以一個(gè)編譯錯(cuò)誤來警示。

2.)Deprecated
???????Deprecated也是一種標(biāo)記類型注解。當(dāng)一個(gè)類型或者類型成員使用@Deprecated修飾的話,編譯器將不鼓勵(lì)使用這個(gè)被標(biāo)注的程序元素。所以使用這種修飾具有一定的“延續(xù)性”:如果我們?cè)诖a中通過繼承或者覆蓋的方式使用了這個(gè)過時(shí)的類型或者成員,雖然繼承或者覆蓋后的類型或者成員并不是被聲明為@Deprecated,但編譯器仍然要報(bào)警。

3.)SuppressWarnings
???????SuppressWarning不是一個(gè)標(biāo)記類型注解。它有一個(gè)類型為String[]的成員,這個(gè)成員的值為被禁止的警告名。對(duì)于javac編譯器來講,被-Xlint選項(xiàng)有效的警告名也同樣對(duì)@SuppressWarings有效,同時(shí)編譯器忽略掉無法識(shí)別的警告名。
  @SuppressWarnings("unchecked")

這種注解的使用非常簡(jiǎn)單,只需在需要注解的地方標(biāo)明某個(gè)注解即可,例如在方法上注解:

public class Test {
    @Override
    public String tostring() {
        return "override it";
    }
}

例如在類上注解:

@Deprecated
public class Test {

}

2、元注解

java.lang.annotation提供了四種元注解,專門注解其他的注解(在自定義注解的時(shí)候,需要使用到元注解):
??????????????@Documented
??????????????@Retention
??????????????@Target
??????????????@Inherited

注解 作用
@Target 表示該注解用于什么地方。如果不明確指出,該注解可以放在任何地方

ElementType.TYPE : 用于描述類、接口或enum聲明
ElementType.FIELD : 成員變量、對(duì)象、屬性(包括enum實(shí)例)
ElementType.METHOD : 用于描述方法
Element.PARAMETER : 用于描述參數(shù)
Element.CONSTRUCTOR : 用于描述構(gòu)造器
Element.LOCAL_VARIABLE : 用于描述局部變量
ElementType.ANNOTATION_TYPE : 用于描述類、接口(包括注解類型) 或enum聲明
ElementType.PACKAGE : 用于描述包
@Retention 定義該注解的生命周期

RetentionPolicy.SOURCE : 在編譯階段丟棄。這些注解在編譯結(jié)束之后就不再有任何意義,所以它們不會(huì)寫入字節(jié)碼(@Override, @SuppressWarnings都屬于這類注解)
RetentionPolicy.CLASS : 在類加載的時(shí)候丟棄。在字節(jié)碼文件的處理中有用。注解默認(rèn)使用這種方式
RetentionPolicy.RUNTIME : 始終不會(huì)丟棄,運(yùn)行期也保留該注解,因此可以使用反射機(jī)制讀取該注解的信息。我們自定義的注解通常使用這種方式
@Document 表示是否將注解信息添加在java文檔中
@Inherited 定義該注釋和子類的關(guān)系

注:如果一個(gè)使用了@Inherited修飾的annotation類型被用于一個(gè)class,則這個(gè)annotation將被用于該class的子類。@Inherited annotation類型是被標(biāo)注過的class的子類所繼承。類并不從它所實(shí)現(xiàn)的接口繼承annotation,方法并不從它所重載的方法繼承annotation

3、自定義注解

自定義注解類編寫的一些規(guī)則:

  1. Annotation型定義為@interface, 所有的Annotation會(huì)自動(dòng)繼承java.lang.Annotation這一接口,并且不能再去繼承別的類或是接口.
  2. 參數(shù)成員只能用public或默認(rèn)(default)這兩個(gè)訪問權(quán)修飾
  3. 參數(shù)成員只能用基本類型byte,short,char,int,long,float,double,boolean八種基本數(shù)據(jù)類型和String、Enum、Class、annotations等數(shù)據(jù)類型,以及這一些類型的數(shù)組.
  4. 要獲取類方法和字段的注解信息,必須通過Java的反射技術(shù)來獲取 Annotation對(duì)象,因?yàn)槟愠酥鉀]有別的獲取注解對(duì)象的方法
  5. 注解也可以沒有定義成員, 不過這樣注解就沒啥用了
    PS:自定義注解需要使用到元注解
 @Target(ElementType.METHOD)
 @Retention(RetentionPolicy.RUNTIME)
 public @interface Test {
   
 }

???????在注解中一般會(huì)有一些元素以表示某些值。注解的元素看起來就像接口的方法,唯一的區(qū)別在于可以為其制定默認(rèn)值。沒有元素的注解稱為標(biāo)記注解,上面的@Test就是一個(gè)標(biāo)記注解。
???????注解的可用的類型包括以下幾種:所有基本類型、String、Class、enum、Annotation、以上類型的數(shù)組形式。元素不能有不確定的值,即要么有默認(rèn)值,要么在使用注解的時(shí)候提供元素的值。而且元素不能使用null作為默認(rèn)值。注解在只有一個(gè)元素且該元素的名稱是value的情況下,在使用注解的時(shí)候可以省略“value=”,直接寫需要的值即可。

四、反射機(jī)制與注解機(jī)制

AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通過反射獲取了某個(gè)類的AnnotatedElement對(duì)象之后,程序就可以調(diào)用該對(duì)象的如下四個(gè)個(gè)方法來訪問Annotation信息:

方法 含義
<T extends Annotation> T getAnnotation(Class<T> annotationClass) 返回改程序元素上存在的、指定類型的注解,如果該類型注解不存在,則返回null
Annotation[] getAnnotations() 返回該程序元素上存在的所有注解。
boolean is AnnotationPresent(Class<?extends Annotation> annotationClass) 判斷該程序元素上是否包含指定類型的注解,存在則返回true,否則返回false
Annotation[] getDeclaredAnnotations() 返回直接存在于此元素上的所有注解。與此接口中的其他方法不同,該方法將忽略繼承的注解。(如果沒有注釋直接存在于此元素上,則返回長(zhǎng)度為零的一個(gè)數(shù)組。)該方法的調(diào)用者可以隨意修改返回的數(shù)組;這不會(huì)對(duì)其他調(diào)用者返回的數(shù)組產(chǎn)生任何影響。
default <T extends Annotation> T[] getAnnotationsByType(Class <T> annotationClass) 返回該元素制定類型的注解
default <T extends Annotation> T getDeclaredAnnotation( Class <T> annotationClass) 返回直接存在與該元素上的所有注解
default <T extends Annotation>T[] getDeclaredAnntationsByType(Class <T> annotationClass) 返回直接存在該元素上某類型的注解

接下來讓我們?cè)诖a中看看二者的結(jié)合

//定義一個(gè)可以注解在Class,interface,enum上的注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnTargetType {
    /**
     * 定義注解的一個(gè)元素 并給定默認(rèn)值
     * @return
     */
    String value() default "MyAnTargetType我是定義在類接口枚舉類上的注解元素value的默認(rèn)值";
}
//定義一個(gè)可以注解在PARAMETER上的注解

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnTargetParameter {
    /**
     * 定義注解的一個(gè)元素 并給定默認(rèn)值
     * @return
     */
    String value() default "MyAnTargetParameter我是定義在參數(shù)上的注解元素value的默認(rèn)值";
}
// 定義一個(gè)可以注解在METHOD上的注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnTargetMethod {
    /**
     * 定義注解的一個(gè)元素 并給定默認(rèn)值
     *
     * @return
     */
    String value() default "MyAnTargetMethod我是定義在方法上的注解元素value的默認(rèn)值";
}
// 定義一個(gè)可以注解在FIELD上的注解

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnTargetField {
    /**
     * 定義注解的一個(gè)元素 并給定默認(rèn)值
     * @return
     */
    String value() default "MyAnTargetField我是定義在字段上的注解元素value的默認(rèn)值";
}
//創(chuàng)建一個(gè)使用了以上四種注解的類

@MyAnTargetType
public class AnnotationTest {
    @MyAnTargetField
    private String field = "我是字段";

    @MyAnTargetMethod("測(cè)試方法")
    public void test(@MyAnTargetParameter String args) {
        System.out.println("參數(shù)值 === " + args);
    }
}
public class Main{

    public static void main(String[] args) {
        // 獲取類上的注解MyAnTargetType
        MyAnTargetType t = AnnotationTest.class.getAnnotation(MyAnTargetType.class);
        System.out.println("類上的注解值 === " + t.value());
        MyAnTargetMethod tm = null;
        try {
            // 根據(jù)反射獲取AnnotationTest類上的test方法
            Method method = AnnotationTest.class.getDeclaredMethod("test", String.class);
            // 獲取方法上的注解MyAnTargetMethod
            tm = method.getAnnotation(MyAnTargetMethod.class);
            System.out.println("方法上的注解值 === " + tm.value());
            // 獲取方法上的所有參數(shù)注解  循環(huán)所有注解找到MyAnTargetParameter注解
            Annotation[][] annotations = method.getParameterAnnotations();

            for (Annotation[] tt : annotations) {
                for (Annotation t1 : tt) {
                    if (t1 instanceof MyAnTargetParameter) {
                        System.out.println("參數(shù)上的注解值 === " + ((MyAnTargetParameter) t1).value());
                    }
                }
            }

            method.invoke(new AnnotationTest(), "改變默認(rèn)參數(shù)");
            // 獲取AnnotationTest類上字段field的注解MyAnTargetField
            MyAnTargetField fieldAn = AnnotationTest.class.getDeclaredField("field").getAnnotation(MyAnTargetField.class);
            System.out.println("字段上的注解值 === " + fieldAn.value());
        } catch (Exception e) {
            e.printStackTrace();
        }

運(yùn)行結(jié)果如下:

類上的注解值 === MyAnTargetType我是定義在類接口枚舉類上的注解元素value的默認(rèn)值
方法上的注解值 === 測(cè)試方法
參數(shù)上的注解值 === MyAnTargetParameter我是定義在參數(shù)上的注解元素value的默認(rèn)值
參數(shù)值 === 改變默認(rèn)參數(shù)
字段上的注解值 === MyAnTargetField我是定義在字段上的注解元素value的默認(rèn)值

最后再寫一個(gè)關(guān)于@Inherited例子:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MyAnTargetType {
    /**
     * 定義注解的一個(gè)元素 并給定默認(rèn)值
     * @return
     */
    String value() default "MyAnTargetType我是定義在類接口枚舉類上的注解元素value的默認(rèn)值";
}
public class Main extends AnnotationTest{

    public static void main(String[] args) {
        MyAnTargetType t2 = Main.class.getAnnotation(MyAnTargetType.class);
        System.out.println("類上的注解值 === "+t2.value());
    }
}

運(yùn)行如下:

類上的注解值 === 我是定義在類接口枚舉類上的注解元素value的默認(rèn)值

說明已經(jīng)獲取到了父類AnnotationTest的注解了
如果MyAnTargetType去掉@Inherited注解運(yùn)行則報(bào)錯(cuò)如下:

Exception in thread "main" java.lang.NullPointerException
    at com.company.Main.main(Main.java:41)

最后借用下別人的Java注解的基礎(chǔ)知識(shí)點(diǎn)導(dǎo)圖


5713484-e2afe3f5096f32e9.png

關(guān)于Java注解機(jī)制的基礎(chǔ)就講完了,歡迎大家前來diss

最后編輯于
?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 什么是注解(Annotation):Annotation(注解)就是Java提供了一種元程序中的元素關(guān)聯(lián)任何信息和...
    九尾喵的薛定諤閱讀 3,393評(píng)論 0 2
  • 對(duì)于java中的思考的方向,1必須要看前端的頁(yè)面,對(duì)于前端的頁(yè)面基本的邏輯,如果能理解最好,不理解也要知道幾點(diǎn)。 ...
    神尤魯?shù)婪?/span>閱讀 896評(píng)論 0 0
  • 本文章涉及代碼已放到github上annotation-study 1.Annotation為何而來 What:A...
    zlcook閱讀 29,729評(píng)論 15 116
  • 整體Retrofit內(nèi)容如下: 1、Retrofit解析1之前哨站——理解RESTful 2、Retrofit解析...
    隔壁老李頭閱讀 8,661評(píng)論 4 31
  • 51.養(yǎng)成自己查看各項(xiàng)政策通知更新的習(xí)慣,拓寬自己的信息渠道。 52.合理規(guī)劃自己的支出收入,做一個(gè)有經(jīng)濟(jì)計(jì)劃的人...
    我叫何奕洺閱讀 191評(píng)論 0 0

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