一、概念
Annontation是Java5開始引入的新特徵。中文名稱一般叫註解,也叫元註解。它提供了一種安全的類似註釋的機制,用來將任何的信息或元數據(metadata)與程序元素(類、方法、成員變量等)進行關聯。
更通俗的意思是爲程序的元素(類、方法、成員變量)加上更直觀更明瞭的說明,這些說明信息是與程序的業務邏輯無關,並且是供指定的工具或框架使用的。
Annontation像一種修飾符一樣,應用於包、類型、構造方法、方法、成員變量、參數及本地變量的聲明語句中
註解的語法比較簡單,除了@符號的使用之外,它基本與Java固有語法一致。
一般是不需要自己去定義註解的,除非你要自己寫框架類的東西,如果是,註解是配合反射一起用的,通過反射,可以根據class、field、method等對象拿到它上面標註的註解,然後根據有沒有註解、註解的類型或註解上的參數的不同,來執行不同的操作。
二、原理
Annotation其實是一種接口。通過Java的反射機制相關的API來訪問annotation信息。相關類(框架或工具中的類)根據這些信息來決定如何使用該程序元素或改變它們的行爲。
annotation是不會影響程序代碼的執行,無論annotation怎麼變化,代碼都始終如一地執行。
Java語言解釋器在工作時會忽略這些annotation,因此在JVM 中這些annotation是“不起作用”的,只能通過配套的工具才能對這些annontaion類型的信息進行訪問和處理。
Annotation與interface的異同:
1)、Annotation類型使用關鍵字@interface而不是interface。
這個關鍵字聲明隱含了一個信息:它是繼承了java.lang.annotation.Annotation接口,並非聲明瞭一個interface
2)、Annotation類型、方法定義是獨特的、受限制的。
Annotation 類型的方法必須聲明爲無參數、無異常拋出的。這些方法定義了annotation的成員:方法名成爲了成員名,而方法返回值成爲了成員的類型。而方法返回值類型必須爲primitive類型、Class類型、枚舉類型、annotation類型或者由前面類型之一作爲元素的一維數組。方法的後面可以使用 default和一個默認數值來聲明成員的默認值,null不能作爲成員默認值,這與我們在非annotation類型中定義方法有很大不同。
Annotation類型和它的方法不能使用annotation類型的參數、成員不能是generic。只有返回值類型是Class的方法可以在annotation類型中使用generic,因爲此方法能夠用類轉換將各種類型轉換爲Class。
3)、Annotation類型又與接口有着近似之處。
它們可以定義常量、靜態成員類型(比如枚舉類型定義)。Annotation類型也可以如接口一般被實現或者繼承。
三、應用場合
annotation一般作爲一種輔助途徑,應用在軟件框架或工具中,在這些工具類中根據不同的 annontation註解信息採取不同的處理過程或改變相應程序元素(類、方法及成員變量等)的行爲。
例如:Junit、Struts、Spring等流行工具框架中均廣泛使用了annontion。使代碼的靈活性大提高。
1.1 知識點
1.1.1 Java內置註解組成
註解的語法比較簡單,除了@符號的使用以外,它基本上與java的固有語法一致,java內置了三種註解,定義在java.lang包中。
註解名稱 |
描述 |
@Override |
表示當前的方法定義將覆蓋超類中的方法。 |
@Deprecated |
使用了註解爲它的元素編譯器將發出警告,因爲註解@Deprecated是不贊成使用的代碼,被棄用的代碼。 |
@SuppressWarnings |
關閉不當編譯器警告信息。 它可以有以下參數: deprecation:過時的類或方法警告。 unchecked:執行了未檢查的轉換時警告。 fallthrough:當 Switch 程序塊直接通往下一種情況而沒有 Break 時的警告。 path:在類路徑、源文件路徑等中有不存在的路徑時的警告。 serial:當在可序列化的類上缺少 serialVersionUID 定義時的警告。 finally:任何 finally 子句不能完成時的警告。 all:關於以上所有情況的警告。 |
1.1.2 如何使用註解
註解的實例代碼如下:
public class Dog { /** * 一個普通的方法,讓此方法變成棄用方法 */ @Deprecated public void } /** * 方法裏有未使用的變量i,在eclipse中有警告提示, * 如果我們想在警告去掉,可以使用@SuppressWarnings註解。 * */ @SuppressWarnings({"all"}) public void eat() { int i = 5; } /** * 重寫Object類中的toString()方法,如果不加@Override, * 本意是想重寫toString(),但如果方法名稱寫錯了也不會報錯, * eclipse認爲這是在子類裏添加了一個新方法,如果加上@Override, * 這就是重寫,父類中必須有這樣的方法。 */ @Override public String toString() { return super.toString(); } } |
1.1.3 創建新註解
和創建一個自定義的類一樣,我們不僅可以使用別人寫好的類,我們也可以創建自已需要的類,註解也一樣,也可以創建系統中不存在的註解。在進行創建新註解時,需要用到原註解,原註解是專門負責新註解的創建的。
原註解共有四個,如下描述:
@Retention
它是被定義在一個註解類的前面,用來說明該註解的生命週期。
它有以下參數:
RetentionPolicy.SOURCE :指定註解只保留在一個源文件當中。
RetentionPolicy.CLASS :指定註解只保留在一個 class 文件中。
RetentionPolicy.RUNTIME :指定註解可以保留在程序運行期間。
@Target
它是被定義在一個註解類的前面,用來說明該註解可以被聲明在哪些元素前。
它有以下參數:
ElementType.TYPE :說明該註解只能被聲明在一個類前。
ElementType.FIELD :說明該註解只能被聲明在一個類的字段前。
ElementType.METHOD :說明該註解只能被聲明在一個類的方法前。
ElementType.PARAMETER :說明該註解只能被聲明在一個方法參數前。
ElementType.CONSTRUCTOR :說明該註解只能聲明在一個類的構造方法前 。
ElementType.LOCAL_VARIABLE :說明該註解只能聲明在一個局部變量前。
ElementType.ANNOTATION_TYPE :說明該註解只能聲明在一個註解類型前 。
ElementType.PACKAGE :說明該註解只能聲明在一個包名前。
@Documented
將註解包含在Javadoc中。
@Inherited
允許子類繼承父類中的註解。
先來看一下如何編寫一個最簡單的註解類:
import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;
@Retention(RetentionPolicy.CLASS) @Target({ElementType.FIELD, ElementType.METHOD}) @Documented @Inherited public @interface Fruit {
} |
通過此註解返回給我們的信息有:
² 當在一個註解類前定義了一個 @Retetion(RetentionPolicy.CLASS) 的註解,那麼說明該註解只保留在一個 class 文件當中,當加載 class 文件到內存時,虛擬機會將註解去掉,從而在程序中不能訪問。
² 說明該註解只能被聲明在一個類的屬性和方法前。注意,如果註解類同時可以用到類和方法前,需要用“{}”引起來編寫。
² 在生成文檔時將註解包含在Javadoc中。
² 允許子類繼承父類中的註解。
除了@符號,註解很像是一個接口。定義註解的時候需要用到元註解,上面用到了@Target、@RetentionPolicy、@Documented、@Inherited四個原註解。
1.1.4 註解定義
1、註解可以有哪些成員?
註解和接口相似,它只能定義 final 靜態屬性和公共抽象方法。
2、註解的方法?
1. 方法前默認會加上 public abstract
2. 在聲明方法時可以定義方法的默認返回值。
例如 :
String color() default "blue";
String[] color() default {"blue", "red",......}
3、方法的返回值可以有哪些類型 ?
八種基本類型, String 、 Class 、枚舉、註解及這些類型的數組。
1.1.5 註解生命週期
一個註解可以有三個生命週期,它默認的生命週期是保留在一個 CLASS 文件,但它也可以由一個@Retetion的元註解指定它的生命週期。
l java 源文件
當在一個註解類前定義了一個@Retetion(RetentionPolicy.SOURCE) 的註解,那麼說明該註解只保留在一個源文件當中,當編譯器將源文件編譯成 class 文件時,它不會將源文件中定義的註解保留在 class 文件中。
l class 文件中
當在一個註解類前定義了一個 @Retetion(RetentionPolicy.CLASS) 的註解,那麼說明該註解只保留在一個 class 文件當中,當加載 class 文件到內存時,虛擬機會將註解去掉,從而在程序中不能訪問。
l 程序運行期間
當在一個註解類前定義了一個 @Retetion(RetentionPolicy.RUNTIME) 的註解,那麼說明該註解在程序運行期間都會存在內存當中。此時,我們可以通過反射來獲得定義在某個類上的所有註解。
1.1.6 註解的使用
在註解中一般會有一些元素以表示某些值。註解的元素看起來就像接口的方法,唯一的區別在於可以爲其制定默認值。沒有元素的註解稱爲標記註解,上面的@Fruit就是一個標記註解。
註解的可用的類型包括以下幾種:所有基本類型、String、Class、enum、Annotation、以上類型的數組形式。
對註解元素的要求:
ü 元素不能有不確定的值,即要麼有默認值,要麼在使用註解的時候提供元素的值。
ü 元素不能使用null作爲默認值。
ü 註解元素必須有確定的值,要麼在定義註解的默認值中指定,要麼在使用註解時指定,非基本類型的註解元素的值不可爲null。因此, 使用空字符串或0作爲默認值是一種常用的做法。這個約束使得處理器很難表現一個元素的存在或缺失的狀態,因爲每個註解的聲明中,所有元素都存在,並且都具有相應的值,爲了繞開這個約束,我們只能定義一些特殊的值,例如空字符串或者負數,一次表示某個元素不存在,在定義註解時,這已經成爲一個習慣用法。
ü 註解在只有一個元素且該元素的名稱是value的情況下,在使用註解的時候可以省略“value=”,直接寫需要的值即可。
繼續使用剛纔的註解類,給裏面添加註解元素,一共添加三個,一個是常量元素,兩個是普通元素,兩個普通元素一個有默認值,一個沒有默認值。
import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;
/** * 註解中有三個元素,一個是常量id,一個是sheel,一個是share,share有默認值。 * @author Administrator * */ @Retention(RetentionPolicy.CLASS) @Target({ElementType.TYPE, ElementType.METHOD}) @Documented @Inherited public @interface Fruit { public final int count = 1; public int sheel(); public String share() default "圓"; } |
定義了註解,必然要去使用註解。對@Fruit類進行的使用:
@Fruit(sheel=1) public class Apple {
} |
因爲在編寫@Fruit時沒有給sheel默認值,所以在使用@Fruit必須要給此元素賦值。有默認值的,我們可以對其進行修改:
@Fruit(sheel=1,share="扁") public class Apple {
} |
注意:常量不可以進行修改。
使用註解最主要的部分在於對註解的處理,那麼就會涉及到註解處理器。從原理上講,註解處理器就是通過反射機制獲取被檢查方法上的註解信息,然後根據註解元素的值進行特定的處理。