夯實Java基礎(十七)——註解(Annotation)

一、註解概述

從JDK5.0開始,Java增長對元數據(MetaData)的支持,也就是註解(Annotation)。其實咱們早就已經接觸過註解了,例如咱們常常在Java代碼中能夠看到 「@Override」,「@Test」等等這樣的東西,它們就是Java中的註解。註解能夠像修飾符同樣使用,能夠用於修飾包、類、構造器、方法、成員變量、參數、局部變量的聲明。html

咱們須要注意的是,註解與註釋是有必定區別的,註解就是代碼裏面的特殊標記,這些標記能夠在編譯,類加載,運行時被讀取,並執行相應的處理。經過註解開發人員能夠在不改變原有代碼和邏輯的狀況下在源代碼中嵌入補充信息。而註釋則是用以說明某段代碼的做用,或者說明某個類的用途、某個方法的功能和介紹,以及該方法的參數和返回值的數據類型及意義等等。java

二、Java內置註解

在JavaSE部分,註解的使用每每比較簡單,Java中提供了5個內置註解,它們分別是:數組

①、@Override:標註該方法是重寫父類中的方法。安全

這個註解一個是咱們見得最多的一個了,提示這個方法是重寫於父類的方法。ide

②、@Deprecated:標記某個功能已通過時,用於定義過期的類、方法、成員變量等。函數

這個註解想必你們應該都有碰到過,在使用Date日期類的時候,裏面有大量過期的方法,咱們來定義一個Date類來調用一個方法。工具

這個getDay()方法就是過期的,咱們點擊進去看一下這個方法的源碼:學習

果真這個方法是用@Deprecated修飾過的。同時也能夠發現咱們在調用過期元素時,編譯器在編譯階段遇到這個註解時會發出提醒警告,告訴開發者正在調用一個過期的元素,固然若是不想看到警告咱們能夠抑制它的出現。測試

③、@SuppressWarnings:抑制編譯器警告。spa

上面說到用@Deprecated修飾過的元素在調用時會有警告,咱們能夠用@SuppressWarnings註解來抑制警告的出現。

能夠發現左邊的警告沒有了。@SuppressWarnings這個註解中參數很是的多,這裏介紹幾個常見的參數:

  • all:抑制全部警告。
  • deprecation:抑制過時方法警告。
  • null:忽略對null的操做。
  • unchecked:抑制沒有進行類型檢查操做的警告。
  • unused:抑制沒被使用過的代碼的警告。

若是須要了解更多的能夠去查看官方文檔。

④、@FunctionaInterface:指定接口必須爲函數式接口。

這個註解是Java8出現的新特性。這個函數式接口的意思就是接口中有一個且僅有一個抽象方法,可是能夠有多個非抽象方法,若是不定義或定義多個抽象方法就會報錯。

正式由於JDK 8中lambda表達式的引入,使得函數式接口在Java中變得愈來愈流行。由於這些特殊類型的接口能夠用lambda表達式、方法引用或構造函數引用輕鬆替換。

⑤、@SafeVarargs:抑制"堆污染警告"。

這個註解是在Java7中引入,主要目的是處理可變長參數中的泛型,此註解告訴編譯器:在可變長參數中的泛型是類型安全的。可變長參數是使用數組存儲的,而數組和泛型不能很好的混合使用。由於數組元素的數據類型在編譯和運行時都是肯定的,而泛型的數據類型只有在運行時才能肯定下來,所以當把一個泛型存儲到數組中時,編譯器在編譯階段沒法檢查數據類型是否匹配,所以會給出警告信息。

咱們來看下面這個示例:

public class Test {
    @SafeVarargs//這裏告訴編譯器類型安全,不讓有警告。其實方法體內容類型不安全
    public static void show(List<String>...lists){
        Object[] arry=lists;
        List<Integer> intList=Arrays.asList(11,22,33);
        arry[0]=intList;//這裏就是堆污染,這裏沒有警告,是由於只針對於可變長參數泛型
        String str=lists[0].get(0);//java.lang.ClassCastException
    }

    public static void main(String[] args) {
        List<String> list1=Arrays.asList("AA","BB","CC");
        List<String> list2=Arrays.asList("DD","EE","DD");
        show(list1,list2);
    }
}

經過上述的示例,咱們將intList賦給array[0],array[0]的類型是List<String>,可是儲引用到實際爲List<Integer>類型的值,這個無效的引用被稱爲堆污染。因爲直到運行時才能肯定此錯誤,所以它會在編譯時顯示爲警告,這裏沒有警告,是由於只針對於可變長參數泛型,並在運行時出現ClassCastException。

注意:@SafeVarargs註解只能用在參數長度可變的方法或構造方法上,且方法必須聲明爲static或final,不然會出現編譯錯誤。

三、自定義註解

咱們在享受註解給咱們帶來方便地同時,咱們本身應該要知道怎麼去定義註解。註解的自定義很是的簡單,經過 @interface關鍵字進行定義,能夠發現這個關鍵字和接口interface很類似,就在前面加了一個 @符號,可是它和接口沒有任何關係。自定義註解還須要注意的一點是:全部的自定義註解都自動繼承了java.lang.annotation.Annotation這個接口。自定義註解的格式:

public @interface 註解名 {
       //屬性
}    

一樣咱們能夠在註解中定義屬性,它的定義有點相似於方法,但又不是方法,在註解中是不能聲明普通方法的。註解的屬性在註解定義中以無參數方法的形式來聲明,其方法名定義了屬性的名字,其返回值定義了該屬性的類型,咱們稱爲配置參數。它們的類型只能是八種基本數據類型、String類型、Class類型、enum類型、Annotation類型以上全部類型的數組。例如:

//定義了一個MyAnnotation註解
public @interface MyAnnotation {
    String[] value();
}

@MyAnnotation(value = "hello")
class Test{

}

上面註解代碼中,定義了一個String的value數組。而後咱們在使用的時候,就可使用 屬性名稱=「xxx」 的形式賦值。

註解中屬性還能夠有默認值,默認值須要用 default 關鍵值指定。好比:.

//定義了一個MyAnnotation註解
public @interface MyAnnotation {
    String id();
    String[] value() default {"AA","BB"};
}

@MyAnnotation(id="one")
class Test{

}

上面定義了 id 屬性沒有默認值,而value屬性中則設置了默認值,因此在使用註解的時候只需給 id 屬性賦值便可,value能夠不用寫。

經過以上形式自定義的註解暫時都尚未任何實用的價值,由於自定義註解必須配上註解的信息處理流程(使用反射)纔有意義。如何讓註解真真的發揮做用,主要就在於註解處理方法,因此接下來咱們將學習元註解和註解的反射。

四、元註解

元註解就是用來修飾其餘註解的註解。咱們隨便點進一個註解的源碼均可以發現有元註解。

Java5.0中定義了4個標準的元註解類型,它們被用來提供對其它註解類型做說明:

  • @Retention
  • @Target
  • @Documented
  • @Inherited

而Java8.0中又增長了一個新的元註解類型:

  • @Repeatable

因此接下來咱們將逐個分析它們的做用和使用方法。

一、@Retention:用於指定該Annotation的生命週期。

這個元註解只能用於修飾一個Annotation定義,它的內部包含了一個RetentionPolicy枚舉類型的屬性,而這個枚舉類中定義了三個枚舉實例,SOURCE、CLASS、RUNTIME。它們各個值的意思以下:

  • RetentionPolicy.SOURCE:在源文件中有效(即源文件保留),在編譯器進行編譯時它將被丟棄忽視。
  • RetentionPolicy.CLASS:在class文件中有效(即class保留),當Java程序運行時,它並不會被加載到 JVM 中,只保留在class文件中。這個是默認值。
  • RetentionPolicy.RUNTIME:在運行時有效(即運行時保留),當Java程序運行時,註解會被加載進入到 JVM 中,因此咱們可使用反射獲取到它們。

 

比較典型的是@SuppressWarnings註解,若是咱們用 javap -c去反編譯它是看到這個註解的,由於在編譯的時候就已經被丟棄了。

②、@Target:用於指定該Annotation可以用在哪些地方。

@Target內部定義了一個枚舉類型的數組ElementType[] value(),在ElementType這個枚舉類中參數有不少,咱們來看一下:

  • TYPE:用於描述類、接口(包括註解類型) 或enum聲明
  • FIELD:用於描述域即類成員變量
  • METHOD:用於描述方法
  • PARAMETER:用於描述參數
  • CONSTRUCTOR:用於描述構造器
  • LOCAL_VARIABLE:用於描述局部變量
  • ANNOTATION_TYPE:因爲描述註解類型
  • PACKAGE:用於描述包
  • TYPE_PARAMETER:1.8版本開始,描述類、接口或enum參數的聲明
  • TYPE_USE:1.8版本開始,描述一種類、接口或enum的使用聲明

③、@Document:表示Annotation能夠被包含到javadoc中去。默認狀況下javadoc是不包含註解的。

因爲這個比較簡單因此不細說。

④、@Inherited:被它修飾的Annotation將具備繼承性。

@Inherited修飾過的Annotation其子類會自動具備該註解。在實際應用中,使用狀況很是少。

⑤、@Repeatable:用於指示它修飾的註解類型是可重複的。

這個註解是在Java8中新出的特性,說到這個可重複註解可能有點不理解。咱們經過示例來理解一下:

先定義一個MyAnnotation註解:

@Inherited
@Documented
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD,ElementType.PARAMETER})
public @interface MyAnnotation {
    String value() default "Hello";
}

這裏須要說明@Repeatable(MyAnnotations.class),它表示在同一個類中@MyAnnotation註解是能夠重複使用的,重複的註解被存放至@MyAnnotations註解中。

而後再定義一個MyAnnotations註解:

@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD,ElementType.PARAMETER})
public @interface MyAnnotations {
    MyAnnotation[] value();
}

這個MyAnnotations註解裏面的屬性必需要聲明爲要重複使用註解的類型數組MyAnnotation[] value();,並且相應的生命週期和使用地方都須要同步。不然就會編譯報錯!

進行測試:

@MyAnnotation(value = "World")
@MyAnnotation(value = "World")
public class Test{

}

而在Java8以前沒有@Repeatable註解是這樣寫的:

@MyAnnotations({@MyAnnotation(value = "World"),@MyAnnotation(value = "World")})
public class Test{

}

五、註解處理器(使用反射)

以上講的因此註解的定義都只是一個形式,實際上還並無什麼可以使用的價值,而註解的核心就是使用反射來獲取到它們,因此下面咱們要來學習註解處理器(使用反射)的使用。

下面參考:https://www.cnblogs.com/peida/archive/2013/04/26/3038503.html

java.lang.reflect 包下主要包含一些實現反射功能的工具類,實際上,java.lang.reflect 包全部提供的反射API擴充了讀取運行時Annotation信息的能力。當一個Annotation類型被定義爲RUNTIME的註解後,該註解才能是運行時可見,當class文件被裝載時被保存在class文件中的Annotation纔會被虛擬機讀取。
AnnotatedElement 接口是全部程序元素(Class、Method和Constructor)的父接口,因此程序經過反射獲取了某個類的AnnotatedElement對象以後,程序就能夠調用該對象的以下四個個方法來訪問Annotation信息:

  • 方法1:<T extends Annotation> T getAnnotation(Class<T> annotationClass): 返回改程序元素上存在的、指定類型的註解,若是該類型註解不存在,則返回null。
  • 方法2:Annotation[] getAnnotations():返回該程序元素上存在的全部註解。
  • 方法3:boolean is AnnotationPresent(Class<?extends Annotation> annotationClass):判斷該程序元素上是否包含指定類型的註解,存在則返回true,不然返回false.
  • 方法4:Annotation[] getDeclaredAnnotations():返回直接存在於此元素上的全部註釋。與此接口中的其餘方法不一樣,該方法將忽略繼承的註釋。(若是沒有註釋直接存在於此元素上,則返回長度爲零的一個數組。)該方法的調用者能夠隨意修改返回的數組;這不會對其餘調用者返回的數組產生任何影響。

一個簡單的註解處理器:  

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)//這裏必須定義爲RUNTIME
public @interface MyAnnotation {
    String id();
    String[] value() default {"AA","BB"};
}

@MyAnnotation(id = "hello")
class Test{
    public static void main(String[] args) {
        boolean annotationPresent = Test.class.isAnnotationPresent(MyAnnotation.class);
        System.out.println(annotationPresent);
        if ( annotationPresent ) {
            MyAnnotation myAnnotation = Test.class.getAnnotation(MyAnnotation.class);

            System.out.println("id:"+myAnnotation.id());
            System.out.println("value:"+ Arrays.toString(myAnnotation.value()));
        }
    }
}

程序運行結果:

上面的例子只是做用在類上面的註解,若是要做用在屬性、方法等上面的註解咱們應該怎麼獲取呢?

定義做用於類上面的MyAnnotation註解:

//做用於類上面的註解
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)//這裏必須定義爲RUNTIME
public @interface MyAnnotation {
    String[] value() default "";
}

定義做用於屬性上面的AttributeAnnotation註解:

//做用於屬性上的註解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)//這裏必須定義爲RUNTIME
public @interface AttributeAnnotation {
    String value();
}

定義做用於方法上面的MethodAnnotation註解:

//做用於方法上的註解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)//這裏必須定義爲RUNTIME
public @interface MethodAnnotation {
    String value();
}

而後就是測試了:

@MyAnnotation(value = "MyAnnotation")
class Test{

    @AttributeAnnotation(value = "AttributeAnnotation")
    String str;

    @MethodAnnotation(value = "MethodAnnotation_show")
    public void show(){
        System.out.println("MethodAnnotation_show");
    }

    @MethodAnnotation(value = "MethodAnnotation_display")
    public void display(){
        System.out.println("MethodAnnotation_display");
    }

    public static void main(String[] args) {
        //獲取類上面的註解
        boolean annotationPresent = Test.class.isAnnotationPresent(MyAnnotation.class);
        System.out.println(annotationPresent);
        if ( annotationPresent ) {
            MyAnnotation myAnnotation = Test.class.getAnnotation(MyAnnotation.class);
            System.out.println("class-annotation:"+Arrays.toString(myAnnotation.value()));
        }

        try {
            //獲取單個屬性中的註解
            Field str = Test.class.getDeclaredField("str");
            AttributeAnnotation attributeAnnotation = str.getAnnotation(AttributeAnnotation.class);
            if (attributeAnnotation!=null){
                System.out.println("attribute-annotation:"+attributeAnnotation.value());
            }

            //獲取多個方法中的註解
            Method[] declaredMethods = Test.class.getDeclaredMethods();
            for (Method declaredMethod : declaredMethods) {
                    MethodAnnotation methodAnnotation = declaredMethod.getAnnotation(MethodAnnotation.class);
                    if (methodAnnotation!=null){
                        System.out.println("method-annotation:"+methodAnnotation.value());
                    }
            }

        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }

    }
}

程序運行結果:

小弟菜鳥只能領悟這麼多了,若是有錯誤或者須要補充的地方歡迎你們留言指出。謝謝!!!

相關文章
相關標籤/搜索