基于上一節,已對注解有了一個基本的認識:注解其實就是一種标記,可以在程式代碼中的關鍵節點(類、方法、變量、參數、包)上打上這些标記,然後程式在編譯時或運作時可以檢測到這些标記進而執行一些特殊操作。是以可以得出自定義注解使用的基本流程:
第一步,定義注解——相當于定義标記;
第二步,配置注解——把标記打在需要用到的程式代碼中;
第三步,解析注解——在編譯期或運作時檢測到标記,并進行特殊操作。
注解類型的聲明部分:
注解在Java中,與類、接口、枚舉類似,是以其聲明文法基本一緻,隻是所使用的關鍵字有所不同<code>@interface</code>。在底層實作上,所有定義的注解都會自動繼承java.lang.annotation.Annotation接口。
注解類型的實作部分:
根據我們在自定義類的經驗,在類的實作部分無非就是書寫構造、屬性或方法。但是,在自定義注解中,其實作部分隻能定義一個東西:注解類型元素(annotation type element)。咱們來看看其文法:
也許你會認為這不就是接口中定義抽象方法的文法嘛?别着急,咱們看看下面這個:
看到關鍵字<code>default</code>了嗎?還覺得是抽象方法嗎?
注解裡面定義的是:注解類型元素!
定義注解類型元素時需要注意如下幾點:
通路修飾符必須為public,不寫預設為public;
該元素的類型隻能是基本資料類型、String、Class、枚舉類型、注解類型(展現了注解的嵌套效果)以及上述類型的一維數組;
該元素的名稱一般定義為名詞,如果注解中隻有一個元素,請把名字起為value(後面使用會帶來便利操作);
()不是定義方法參數的地方,也不能在括号中定義任何參數,僅僅隻是一個特殊的文法;
default代表預設值,值必須和第2點定義的類型一緻;
如果沒有預設值,代表後續使用注解時必須給該類型元素指派。
可以看出,注解類型元素的文法非常奇怪,即又有屬性的特征(可以指派),又有方法的特征(打上了一對括号)。但是這麼設計是有道理的,我們在後面的章節中可以看到:注解在定義好了以後,使用的時候操作元素類型像在操作屬性,解析的時候操作元素類型像在操作方法。
一個最最基本的注解定義就隻包括了上面的兩部分内容:1、注解的名字;2、注解包含的類型元素。但是,我們在使用JDK自帶注解的時候發現,有些注解隻能寫在方法上面(比如@Override);有些卻可以寫在類的上面(比如@Deprecated)。當然除此以外還有很多細節性的定義,那麼這些定義該如何做呢?接下來就該元注解出場了!
元注解:專門修飾注解的注解。它們都是為了更好的設計自定義注解的細節而專門設計的。我們為大家一個個來做介紹。
@Target注解,是專門用來限定某個自定義注解能夠被應用在哪些Java元素上面的。它使用一個枚舉類型定義如下:
@Retention注解,翻譯為持久力、保持力。即用來修飾自定義注解的生命力。
注解的生命周期有三個階段:1、Java源檔案階段;2、編譯到class檔案階段;3、運作期階段。同樣使用了RetentionPolicy枚舉類型定義了三個階段:
我們再詳解一下:
如果一個注解被定義為RetentionPolicy.SOURCE,則它将被限定在Java源檔案中,那麼這個注解即不會參與編譯也不會在運作期起任何作用,這個注解就和一個注釋是一樣的效果,隻能被閱讀Java檔案的人看到;
如果一個注解被定義為RetentionPolicy.CLASS,則它将被編譯到Class檔案中,那麼編譯器可以在編譯時根據注解做一些處理動作,但是運作時JVM(Java虛拟機)會忽略它,我們在運作期也不能讀取到;
如果一個注解被定義為RetentionPolicy.RUNTIME,那麼這個注解可以在運作期的加載階段被加載到Class對象中。那麼在程式運作階段,我們可以通過反射得到這個注解,并通過判斷是否有這個注解或這個注解中屬性的值,進而執行不同的程式代碼段。我們實際開發中的自定義注解幾乎都是使用的RetentionPolicy.RUNTIME;
在預設的情況下,自定義注解是使用的RetentionPolicy.CLASS。
@Documented注解,是被用來指定自定義注解是否能随着被定義的java檔案生成到JavaDoc文檔當中。
@Inherited注解,是指定某個自定義注解如果寫在了父類的聲明部分,那麼該父類下的子類的聲明部分也能自動擁有該注解。@Inherited注解隻對那些@Target被定義為ElementType.TYPE的自定義注解起作用。
回顧一下注解的使用流程:
到目前為止我們隻是完成了第一步,接下來我們就來學習第二步,配置注解,如何在另一個類當中配置它。
首先,定義一個注解、和一個供注解修飾的簡單Java類
簡單分析下:
MyAnnotation的@Target定義為ElementType.METHOD,那麼它書寫的位置應該在方法定義的上方,即:public void study(int times)之上;
由于我們在MyAnnotation中定義的有注解類型元素,而且有些元素是沒有預設值的,這要求我們在使用的時候必須在标記名後面打上(),并且在()内以“元素名=元素值“的形式挨個填上所有沒有預設值的注解類型元素(有預設值的也可以填上重新指派),中間用“,”号分割;
是以最終書寫形式如下:
特殊文法一:
如果注解本身沒有注解類型元素,那麼在使用注解的時候可以省略(),直接寫為:@注解名,它和标準文法@注解名()等效!
特殊文法二:
如果注解本本身隻有一個注解類型元素,而且命名為value,那麼在使用注解的時候可以直接使用:@注解名(注解值),其等效于:@注解名(value = 注解值)
特殊用法四:
如果一個注解的@Target是定義為Element.PACKAGE,那麼這個注解是配置在package-info.java中的,而不能直接在某個類的package代碼上面配置。
這一章是使用注解的核心,讀完此章即可明白,如何在程式運作時檢測到注解,并進行一系列特殊操作!
首先回顧一下,之前自定義的注解@myAnnotation,并把它配置在了類Student上,代碼如下:
注解保持力的三個階段:
Java源檔案階段;
編譯到class檔案階段;
運作期階段。
隻有當注解的保持力處于運作階段,即使用<code>@Retention(RetentionPolicy.RUNTIME)</code>修飾注解時,才能在JVM運作時,檢測到注解,并進行一系列特殊操作。
是以,明确我們的目标:在運作期探究和使用編譯期的内容(編譯期配置的注解),要用到Java中的靈魂技術——反射!
解釋一下:
如果我們要獲得的注解是配置在方法上的,那麼我們要從Method對象上擷取;如果是配置在屬性上,就需要從該屬性對應的Field對象上去擷取,如果是配置在類型上,需要從Class對象上去擷取。總之在誰身上,就從誰身上去擷取!
isAnnotationPresent(Class<? extends Annotation> annotationClass)方法是專門判斷該元素上是否配置有某個指定的注解;
<code>getAnnotation(Class<A> annotationClass)</code>方法是擷取該元素上指定的注解。之後再調用該注解的注解類型元素方法就可以獲得配置時的值資料;
反射對象上還有一個方法getAnnotations(),該方法可以獲得該對象身上配置的所有的注解。它會傳回給我們一個注解數組,需要注意的是該數組的類型是Annotation類型,這個Annotation是一個來自于java.lang.annotation包的接口。