天天看點

Java核心技術點之注解一、什麼是注解二、元注解三、常見内建注解四、自定義注解五、注解的解析六、參考資料

Java核心技術點之注解一、什麼是注解二、元注解三、常見内建注解四、自定義注解五、注解的解析六、參考資料

我們大家都知道java代碼中使用注釋是為了向以後閱讀這份代碼的人解釋說明一些事情,注解是注釋的更新版,它可以向編譯器、虛拟機等解釋說明一些事情。比如我們非常熟悉的@override就是一種元注解,它的作用是告訴編譯器它所注解的方法是重寫父類的方法,這樣編譯器就會去檢查父類是否存在這個方法,以及這個方法的簽名與父類是否相同。

也就是說,注解是描述java代碼的代碼,它能夠被編譯器解析,注解處理工具在運作時也能夠解析注解。我們在java源檔案中使用注釋,是為了以後我們或他人再來讀這段代碼時,能夠更好地了解它。javadoc工具可以解析我們在源代碼中為類、方法、變量等添加的描述資訊,并根據這些描述資訊自動生成一個html文檔,這些自動生成的文檔即可作為api幫助文檔。隻要我們為類、方法等添加的描述資訊符合javadoc要求的文法,我們就能夠使用javadoc工具根據我們的描述資訊自動生成一個幫助文檔。而注解比java注釋和javadoc要強大得多,它們三者之間的重大的差別在于,java注釋和javadoc描述所發揮的作用僅僅到編譯時就止步了,而注解直到運作時都能夠發揮作用。

我們知道,使用“transient”關鍵字可以告訴編譯器這個域不可序列化。相比于用”transient“這樣的關鍵字修飾一個屬性,注解為我們提供了為類/方法/屬性/變量添加描述資訊的更通用的方式,而這些描述資訊對于開發者、自動化工具、java編譯器和java運作時來說都是有意義的,也就是說他們都能“讀懂”注解資訊。”transient“關鍵字是一個修飾符,而注解也是一種修飾符。除了傳遞資訊,我們也可以使用注解生成代碼。我們可以使用注解,然後讓注解解析工具來解析它們,以此來生成一些”模闆化“的代碼。比如hibernate、spring、axis這些架構大量使用了注解,來避免一些重複的工作。

    元注解即用來描述注解的注解,比如以下代碼中我們使用“@target”元注解來說明methodinfo這個注解隻能應用于對方法進行注解:

1

2

3

4

<code>@target</code><code>(elementtype.method)</code>

<code>public</code> <code>@interface</code> <code>methodinfo {</code>

<code>    </code><code>...</code>

<code>}</code>

下面我們來具體介紹一下幾種元注解。

<a></a>

當一個注解類型被@documented元注解所描述時,那麼無論在哪裡使用這個注解,都會被javadoc工具文檔化。我們來看一下它的定義:

5

<code>@documented</code>

<code>@retention</code><code>(retentionpolicy.runtime)</code>

<code>@target</code><code>(elementtype.annotation_type)</code>

<code>public</code> <code>@interface</code> <code>documented {</code>

我們從以上代碼中可以看到,定義注解使用@interface關鍵字,這就好比我們定義類時使用class關鍵字,定義接口時使用interface關鍵字一樣,注解也是一種類型。這個元注解被@documented修飾,表示它本身也會被文檔化。@retention元注解的值retentionpolicy.runtime表示@documented這個注解能保留到運作時;@target元注解的值elementtype.annotation_type表示@documented這個注解隻能夠用來描述注解類型。

表明被修飾的注解類型是自動繼承的。具體解釋如下:若一個注解類型被inherited元注解所修飾,則當使用者在一個類聲明中查詢該注解類型時,若發現這個類聲明中不包含這個注解類型,則會自動在這個類的父類中查詢相應的注解類型,這個過程會被重複,直到該注解類型被找到或是查找完了object類還未找到。這個元注解的定義如下:

<code>public</code> <code>@interface</code> <code>inherited {</code>

我們可以看到這個元注解類型被@documented所注解,能夠保留到運作時,隻能用來描述注解類型。

我們在上面已經見到個這個元注解,它表示一個注解類型會被保留到什麼時候,比如以下代碼表示developer注解會被保留到運作時:

<code>public</code> <code>@interface</code> <code>developer {</code>

<code>    </code><code>string value();</code>

@retention元注解的定義如下:

6

<code>public</code> <code>@interface</code> <code>retention {</code>

<code>    </code><code>retentionpolicy value();</code>

我們在使用@retention時,後面括号裡的内容即表示他的取值,從以上定義我們可以看到,取值的類型為retentionpolicy,這是一個枚舉類型,它可以取以下值:

source:表示在編譯時這個注解會被移除,不會包含在編譯後産生的class檔案中;

class:表示這個注解會被包含在class檔案中,但在運作時會被移除;

runtime:表示這個注解會被保留到運作時,在運作時可以jvm通路到,我們可以在運作時通過反射解析這個注解。

這個元注解說明了被修飾的注解的應用範圍,也就是被修飾的注解可以用來注解哪些程式元素,它的定義如下:

<code>public</code> <code>@interface</code> <code>target {</code>

<code>    </code><code>elementtype[] value();</code>

從以上定義我們可以看到它也會保留到運作時,而且它的取值是為elementtype[]類型(一個數組,意思是可以指定多個值),elementtype是一個枚舉類型,它可以取以下值:

 type:表示可以用來注解類、接口、注解類型或枚舉類型;

package:可以用來注解包;

parameter:可以用來注解參數;

annotation_type:可以用來注解 注解類型;

method:可以用來注解方法;

field:可以用來注解屬性(包括枚舉常量);

constructor:可以用來注解構造器;

local_variable:可用來注解局部變量。

java本身内建了一些注解,下面我們來介紹一下我們在日常開發中比較常見的注解:@override、@deprecated、@suppresswarnings。相信我們大家或多或少都使用過這三個注解,下面我們一起再重新認識一下它們。

我們先來看一下這個注解類型的定義:

<code>@retention</code><code>(retentionpolicy.source)</code>

<code>public</code> <code>@interface</code> <code>override {</code>

從它的定義我們可以看到,這個注解可以被用來修飾方法,并且它隻在編譯時有效,在編譯後的class檔案中便不再存在。這個注解的作用我們大家都不陌生,那就是告訴編譯器被修飾的方法是重寫的父類的中的相同簽名的方法,編譯器會對此做出檢查,若發現父類中不存在這個方法或是存在的方法簽名不同,則會報錯。

這個注解的定義如下:

<code>@target</code><code>(value={constructor, field, local_variable, method, package, parameter, type})</code>

<code>public</code> <code>@interface</code> <code>deprecated {</code>

從它的定義我們可以知道,它會被文檔化,能夠保留到運作時,能夠修飾構造方法、屬性、局部變量、方法、包、參數、類型。這個注解的作用是告訴編譯器被修飾的程式元素已被“廢棄”,不再建議使用者使用。

這個注解我們也比較常用到,先來看下它的定義:

<code>@target</code><code>({type, field, method, parameter, constructor, local_variable})</code>

<code>public</code> <code>@interface</code> <code>suppresswarnings {</code>

<code>    </code><code>string[] value();</code>

它能夠修飾的程式元素包括類型、屬性、方法、參數、構造器、局部變量,隻能存活在源碼時,取值為string[]。它的作用是告訴編譯器忽略指定的警告資訊,它可以取的值如下所示:

deprecation:忽略使用了廢棄的類或方法時的警告;

unchecked:執行了未檢查的轉換;

fallthrough:swich語句款中case忘加break進而直接“落入”下一個case;

path:類路徑或原檔案路徑等不存在;

serial:可序列化的類缺少serialversionuid;

finally:存在不能正常執行的finally子句;

all:以上所有情況産生的警告均忽略。

這個注解的使用示例如下:

<code>@suppresswarning</code><code>(value={</code><code>"deprecation"</code><code>,</code><code>"unchecked"</code><code>})</code>

<code>public</code> <code>void</code> <code>mymethos() {...}</code>

通過使用以上注解,我們告訴編譯器忽略mymethod方法中由于使用了廢棄的類或方法或是做了未檢查的轉換而産生的警告。

我們可以建立我們自己的注解類型并使用它。請看下面的示例:

7

8

9

<code>@inherited</code>

<code>    </code><code>string author()</code><code>default</code> <code>"absfree"</code><code>;</code>

<code>    </code><code>string date();</code>

<code>    </code><code>int</code> <code>version()</code><code>default</code> <code>1</code><code>;</code>

在自定義注解時,有以下幾點需要我們了解:

注解類型是通過”@interface“關鍵字定義的;

在”注解體“中,所有的方法均沒有方法體且隻允許public和abstract這兩種修飾符号(不加修飾符預設為public),注解方法不允許有throws子句;

注解方法的傳回值隻能為以下幾種:原始資料類型), string, class, 枚舉類型, 注解和它們的一維數組,可以為方法指定預設傳回值。

我們再把上面提到過的@suppresswarnings這個注解類型的定義拿出來看一下,這個注解類型是系統為我們定義好的,它的定義如下:

我們可以看到,它隻定義了一個注解方法value(),它的傳回值類型為string[],沒有指定預設傳回值。我們使用@suppresswarnings這個注解所用的文法如下:

<code>@suppresswarnings</code><code>(value={</code><code>"value1"</code><code>,</code><code>"value2"</code><code>, ...})</code>

也就是在注解類型名稱後的括号内為每個注解方法指定傳回值就可以使用這個注解。下面我們來看看怎麼使用我們自定義的注解類型@methodinfo:

<code>public</code> <code>class</code> <code>annotationtest {</code>

<code>    </code><code>@methodinfo</code><code>(author=</code><code>"absfree"</code><code>, date=</code><code>"20160410"</code><code>)</code>

<code>    </code><code>public</code> <code>static</code> <code>void</code> <code>main(string[] args) {</code>

<code>        </code><code>system.out.println(</code><code>"using custom annotation..."</code><code>);</code>

<code>    </code><code>}</code>

那麼現在問題來了,我們使用的自定義注解對于編譯器或是虛拟機來說是有意義的嗎(編譯器或是虛拟機能讀懂嗎)?顯然我們什麼都不做的話,編譯器或者虛拟機是讀不懂我們的自定義注解的。下面我們來介紹以下注解的解析,讓編譯器或虛拟機能夠讀懂我們的自定義注解。 

編譯時注解指的是@retention的值為class的注解,對于這類注解的解析,我們隻需做以下兩件事:

自定義類繼承 abstractprocessor類;

重寫其中的 process 函數。

實際上,編譯器在編譯時會自動查找所有繼承自 abstractprocessor 的類,然後調用他們的 process

方法。是以我們隻要做好上面兩件事,編譯器就會主動去解析我們的編譯時注解。現在,我們把上面定義的methodinfo的retention改為class,我們就可以按照以下代碼來解析它:

10

11

12

13

14

<code>@supportedannotationtypes</code><code>({</code><code>"com.custom.customannotation.methodinfo"</code> <code>})</code>

<code>public</code> <code>class</code> <code>methodinfoprocessor</code><code>extends</code> <code>abstractprocessor {</code>

<code>    </code><code>@override</code>

<code>    </code><code>public</code> <code>boolean</code> <code>process(set&lt;?</code><code>extends</code> <code>typeelement&gt; annotations, roundenvironment env) {</code>

<code>        </code><code>hashmap&lt;string, string&gt; map =</code><code>new</code> <code>hashmap&lt;string, string&gt;();</code>

<code>        </code><code>for</code> <code>(typeelement typeelement : annotations) {</code>

<code>            </code><code>for</code> <code>(element element : env.getelementsannotatedwith(typeelement)) {</code>

<code>                </code><code>methodinfo methodinfo = element.getannotation(methodinfo.</code><code>class</code><code>);</code>

<code>                </code><code>map.put(element.getenclosingelement().tostring(), methodinfo.author());</code>

<code>            </code><code>}</code>

<code>        </code><code>}</code>

<code>        </code><code>return</code> <code>false</code><code>;</code>

@supportedannotationtypes注解描述了processor要解析的注解的名字。process

函數的annotations參數表示

表示待處理的注解集,env表示目前或是之前的運作環境。process函數的傳回值表示annotations中的注解是否被這個processor接受。

首先我們把methodinfo注解類型中retention的值改回原來的runtime,接下來我們介紹如何通過反射機制在運作時解析我們的自定義注解類型。

java.lang.reflect包中有一個annotatedelement接口,這個接口定義了用于擷取注解資訊的幾個方法:

<code>t getannotation(class annotationclass)</code><code>//傳回該程式元素的指定類型的注解,若不存在這個類型的注解則傳回null</code>

<code>annotation[] getannotations()</code><code>//傳回修飾該程式元素的所有注解</code>

<code>annotation[] getdeclaredannotations()</code><code>//傳回直接修飾該元素的所有注解</code>

<code>boolean</code> <code>isannotationpresent(class&lt;?</code><code>extends</code> <code>annotation&gt; annotationclass)</code><code>//當該程式元素被指定類型注解修飾時,傳回true,否則傳回false</code>

解析我們上面的自定義注解methodinfo的相關示例代碼如下(annotationparser.java):

15

16

17

18

<code>public</code> <code>class</code> <code>annotationparser {</code>

<code>        </code><code>try</code> <code>{</code>

<code>            </code><code>class cls = annotationtest.</code><code>class</code><code>;</code>

<code>            </code><code>for</code> <code>(method method : cls.getmethods()) {</code>

<code>                </code><code>methodinfo methodinfo = method.getannotation(methodinfo.</code><code>class</code><code>);</code>

<code>                </code><code>if</code> <code>(methodinfo !=</code><code>null</code><code>) {</code>

<code>                    </code><code>system.out.println(</code><code>"method name:"</code> <code>+ method.getname());</code>

<code>                    </code><code>system.out.println(</code><code>"method author:"</code> <code>+ methodinfo.author());</code>

<code>                    </code><code>system.out.println(</code><code>"method date:"</code> <code>+ methodinfo.date());</code>

<code>                    </code><code>system.out.println(</code><code>"method version:"</code> <code>+ methodinfo.version());</code>

<code>                </code><code>}</code>

<code>        </code><code>}</code><code>catch</code> <code>(exception e) {</code>

<code>            </code><code>e.printstacktrace();</code>

運作以上代碼我們可以得到以下輸出:

Java核心技術點之注解一、什麼是注解二、元注解三、常見内建注解四、自定義注解五、注解的解析六、參考資料

這說明我們已經成功解析了自定義注解。關于注解有點我們需要明确的是,作為描述代碼本身的一種中繼資料,注解是一種”被動“的資訊。也就是說,必須由編譯器或虛拟機來“主動”解析它,它才能發揮自己的作用。

1. java documention

作者:absfree

來源:51cto