?????注解(Annotation)就是一種標(biāo)簽给梅,可以插入到源代碼中,我們的編譯器可以對(duì)他們進(jìn)行邏輯判斷飒筑,或者我們可以自己寫一個(gè)工具方法來讀取我們?cè)创a中的注解信息离例,從而實(shí)現(xiàn)某種操作。需要申明一點(diǎn)凡怎,注解不會(huì)改變編譯器的編譯方式校焦,也不會(huì)改變虛擬機(jī)指令執(zhí)行的順序,它更可以理解為是一種特殊的注釋统倒,本身不會(huì)起到任何作用寨典,需要工具方法或者編譯器本身讀取注解的內(nèi)容繼而控制進(jìn)行某種操作。本篇文章將從以下幾點(diǎn)詳細(xì)的介紹下Java注解的使用:
- 元數(shù)據(jù)和注解(Annotation)
- 按照參數(shù)個(gè)數(shù)分類注解(標(biāo)記房匆,單值耸成,完整)
- 按照注解使用途徑分類(標(biāo)準(zhǔn),元注解浴鸿,自定義)
- 自定義注解處理器完成讀取注解內(nèi)容的操作
一井氢、元數(shù)據(jù)和注解
?????元數(shù)據(jù)(meta-data)就是指用來描述數(shù)據(jù)的數(shù)據(jù),它往往是以標(biāo)簽的形式出現(xiàn)赚楚,主要用于描述代碼塊之間的聯(lián)系毙沾。我們的注解就是一種元數(shù)據(jù),根據(jù)它所起到的作用宠页,我們可以大致將它分為以下三類:
- 編寫文檔:通過代碼中標(biāo)識(shí)的元數(shù)據(jù)生成文檔
- 代碼分析:通過代碼中的元數(shù)據(jù)獲取其中信息內(nèi)容
- 編譯檢查:通過標(biāo)記注解可以完成對(duì)代碼塊的檢查左胞,例如:@Override,用于檢查格式
二举户、標(biāo)準(zhǔn)注解(系統(tǒng)自帶)
?????在我們jdk的java.lang包中定義了三個(gè)注解烤宙,他們是:@Override,@Deprecated俭嘁,@SuppressWarnnings躺枕。Override這個(gè)注解我們經(jīng)常會(huì)使用到,在子類重寫父類方法的時(shí)候就會(huì)使用到,他會(huì)幫助我們校驗(yàn)格式拐云,確保我們正在定義的方法是在重寫了父類的對(duì)應(yīng)方法罢猪。Deprecated注解一般修飾在類或者方法之前,用于表示該方法或者類已經(jīng)不再推薦使用了叉瘩。SuppressWarnnings注解主要用于抑制編譯器警告膳帕,具體的我們簡(jiǎn)單的演示下。
public class People {
public void sayHello(){
System.out.println("helo walker");
}
}
public class Student extends People {
/*@Override
public void sayHello(){
System.out.println("hello yam");
}這樣是沒有問題的*/
@Override
public void say(){
System.out.println("hello yam");
}/*如果你定義的方法不能重寫父類某個(gè)方法薇缅,要么拼寫錯(cuò)誤危彩,參數(shù)個(gè)數(shù),方法名不一樣等泳桦,編譯拋出警告*/
}
我們需要注意的是汤徽,這里的override注解只能用于修飾方法,不能用于修飾類或者域灸撰。
public class Student extends People {
@Deprecated
public void say(){
System.out.println("hello yam");
}
}
//調(diào)用過時(shí)方法
public static void main(String[] args){
Student s = new Student();
s.say();
}
雖然編譯時(shí)拋出了警告谒府,但是程序依然可以正常的運(yùn)行結(jié)束。此注解只是告知用戶被標(biāo)記的方法或者類已經(jīng)不再推薦使用梧奢,但是你依然是可以使用的狱掂。之所以建議不再使用,一定是有了更好的取代物了亲轨,如果你一定要在你的項(xiàng)目中使用趋惨,等待新的jdk版本發(fā)布之后,很可能刪除了這些方法或者類惦蚊,可能會(huì)導(dǎo)致你的項(xiàng)目原先的一些方法或者類無法識(shí)別器虾。
@SuppressWarnings("deprecation")
public static void main(String[] args){
Student s = new Student();
s.say();
}
例如,我們可以使用SuppressWarnings注解蹦锋,阻止彈出過時(shí)警告兆沙。關(guān)于SuppressWarnings的參數(shù)主要有以下幾種:
- deprecation:使用了不贊成使用的類或方法時(shí)的警告
- unchecked:執(zhí)行了未檢查的轉(zhuǎn)換時(shí)的警告,例如當(dāng)使用集合時(shí)沒有用泛型 (Generics) 來指定集合保存的類型;
- fallthrough:當(dāng) Switch 程序塊直接通往下一種情況而沒有 Break 時(shí)的警告;
- path:在類路徑莉掂、源文件路徑等中有不存在的路徑時(shí)的警告;
- serial:當(dāng)在可序列化的類上缺少 serialVersionUID 定義時(shí)的警告;
- finally:任何 finally 子句不能正常完成時(shí)的警告;
- all:關(guān)于以上所有情況的警告葛圃。
三、元注解
?????元注解就是用來注解注解的注解憎妙。定義可能有點(diǎn)繞库正,其實(shí)元注解是一種注解,他可以加在一般的注解上用于限制該注解的使用范圍厘唾,生命周期等褥符。一般在自定義注解時(shí)候使用的多。在jdk的中java.lang.annotation包中定義了四個(gè)元注解:
- @Target:指定被修飾的注解的作用范圍
- @Retention:指定了被修飾的注解的生命周期
- @Documented:指定了被修飾的注解是可以被例如Javadoc等工具文檔化的
- @Inherited:指定了被修飾的注解修飾程序元素的時(shí)候是可以被子類繼承的
我們首先看看@Target的使用:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
這是系統(tǒng)注解Override的定義源代碼抚垃,我們看到Target注解中參數(shù)ElementType.METHOD表示該注解只能用于修飾方法喷楣。使用Target注解限定了Override的修飾范圍只能使方法趟大,不能是類或者域。Target還有一些其他的參數(shù):
- CONSTRUCTOR:用于描述構(gòu)造器
- FIELD:用于描述域
- LOCAL_VARIABLE:用于描述局部變量
- METHOD:用于描述方法
- PACKAGE:用于描述包
- PARAMETER:用于描述參數(shù)
- TYPE:用于描述類铣焊、接口(包括注解類型) 或enum聲明
通過上述的參數(shù)我們可以在定義一個(gè)注解的時(shí)候限定他的作用范圍逊朽。
下面看看Retention這個(gè)元注解,依然以注解Override為例曲伊,
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
我們看到Retention中使用了參數(shù)RetentionPolicy.SOURCE惋耙,這個(gè)參數(shù)表示該注解只在源代碼中有效,進(jìn)過編譯之后將會(huì)被丟棄熊昌。還有一些其他參數(shù):
- SOURCE:在源文件中有效(即源文件保留)
- CLASS:在class文件中有效(即class保留)
- RUNTIME:在運(yùn)行時(shí)有效(即運(yùn)行時(shí)保留)
SOURCE表示編譯器編譯之后的class文件中是不存在這一行注解代碼的,CLASS范圍表示編譯器編譯之后湿酸,注解代碼存在于class文件中婿屹,但是jvm在加載此class文件的時(shí)候會(huì)自動(dòng)忽略掉這一行注解代碼。RUNTIME表示jvm加載class文件的時(shí)候會(huì)被讀取到內(nèi)存推溃,也就是運(yùn)行時(shí)保留昂利。
接著使注解Documented,這是一個(gè)關(guān)于文檔的元注解铁坎,被它注解的注解在注解其他方法或者類的時(shí)候可以被Javadoc等工具文檔化蜂奸,對(duì)于一般的注解,在Javadoc等工具文檔化類或者方法的時(shí)候會(huì)丟棄注解內(nèi)容硬萍,使用它就可以使得文檔化的時(shí)候依然保存著注解代碼扩所。
//Test是一個(gè)被元注解Documented修飾
public class User {
@Test(value = 10,description = "do something")
public void test1() {
}
}
使用Javadoc生成API:
類User中的方法test1方法的頭部是保留著注解的,如果是一般的注解則不會(huì)保留朴乖。
最后是元注解Inherited祖屏,我們知道如果一個(gè)普通的注解修飾了一個(gè)父類,那么他的子類是不能繼承修飾父類的注解的买羞。
@Deprecated
public class People {
public void sayHello(){
System.out.println("helo walker");
}
}
public class Student extends People {
public void say(){
System.out.println("hello yam");
}
}
我們可以看到袁勺,在父類people上使用了注解Deprecated,people類名上是有刪除線的(粘貼到此處并沒有顯示)表示此類不推薦使用畜普,但是我們可以看到在子類Student上是沒有刪除線的期丰,也就是父類廢棄了,子類依然是正常的吃挑。(注解不會(huì)被繼承)钝荡,但是如果我們希望子類能夠繼承父類的某些注解,那么只需要在定義該注解的時(shí)候使用我們的元注解Inherited修飾即可儒鹿。
四化撕、自定義注解
?????以上我們看到的標(biāo)準(zhǔn)注解,元注解都是jdk中定義好了的约炎,如果我們想要自定義一個(gè)自己的注解就需要通過@interface來定義一個(gè)全新的注解植阴。
//定義一個(gè)注解
public @interface myAnnotion {
}
使用@interface定義一個(gè)注解的時(shí)候蟹瘾,會(huì)自動(dòng)繼承java.lang.annotation.Annotation接口,以下是其中的一些方法:
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
我們自定義的注解掠手,除了多了個(gè)@符號(hào)憾朴,其他的和定義一個(gè)接口是一樣的,所以這些方法我們不用實(shí)現(xiàn)喷鸽。以上我們定義的是一個(gè)沒有注解體的一個(gè)注解众雷,像這樣的注解我們叫做標(biāo)記注解,這是表示一種標(biāo)記做祝,編譯器根據(jù)某個(gè)類或方法是否具有此標(biāo)記來判斷是否要添加一些代碼或做一定的檢測(cè)操作砾省。例如:@Override注解就是一個(gè)標(biāo)記注解,如果某個(gè)方法前被修飾了此注解混槐,編譯器在編譯時(shí)會(huì)找到父類编兄,判斷對(duì)應(yīng)的方法是否完成了重寫的格式。
?????下面聲明了一個(gè)具有注解體的注解:
public @interface myAnnotion {
String name() default "";
int age();
}
我們說過声登,聲明注解和聲明接口很是類似狠鸳,所以注解中的所有參數(shù)都必須以抽象方法的形式存在,例如上面一樣悯嗓。接下來我們看如何使用該注解:
@myAnnotion(name = "walker",age=10)
public class Test_ann {
public static void main(String[] args){
}
}
之前我們說過件舵,注解本身不會(huì)起到任何作用,需要配合注解處理器才能發(fā)揮一定的作用脯厨,自己本身其實(shí)更像是一種特殊的注釋铅祸。在上例中,我們可以在()中為注解的內(nèi)部參數(shù)賦值俄认,需要注意的是个少,注解的參數(shù)不允許為null,也就是在使用注解的時(shí)候眯杏,內(nèi)部的每個(gè)參數(shù)都是必須要有數(shù)值的夜焦,要么在定義的時(shí)候給賦上默認(rèn)值(使用default關(guān)鍵字),要么在()內(nèi)顯式的賦值岂贩。允許的注解參數(shù)類型有:
- 所有基本數(shù)據(jù)類型(int,float,boolean,byte,double,char,long,short)
- String類型
- Class類型
- enum類型
- Annotation類型
- 以上所有類型的數(shù)組
如果我們想要表示注解中某個(gè)參數(shù)不存在茫经,該怎么辦呢?比如我們用上述自定義的注解去修飾了一個(gè)People類萎津,如果此人的age不知道卸伞,我們?cè)撊绾钨x值(參數(shù)的值不能為null)。我們往往用一些特殊值來標(biāo)記某個(gè)參數(shù)不存在的情況锉屈,例如我們可以給age賦值-1表示此人年齡不詳荤傲,在使用注解處理器讀取的時(shí)候發(fā)現(xiàn)age等于-1,我們就知道此人年齡不詳颈渊。往往字符串類型的參數(shù)用""表示參數(shù)不存在遂黍,整型類型參數(shù)使用負(fù)數(shù)表示參數(shù)不存在终佛。
五、使用注解處理器響應(yīng)注解
?????我們說過一個(gè)注解被定義出來之后雾家,是不能完成任何作用的铃彰,如果沒有注解處理器響應(yīng)的注解和注釋差不多。本小節(jié)我們看看如何定義一個(gè)注解處理器來對(duì)我們自定義的注解進(jìn)行響應(yīng)芯咧。還有一個(gè)前提是:我們的注解處理器實(shí)際上也是類牙捉,所以它只有在被加載到j(luò)vm中才能生效,但是如果我們的注解的生命周期范圍到不了jvm的話敬飒,注解處理器也是沒用的邪铲。
?????Java擴(kuò)充了其反射機(jī)制,使得我們可以利用反射來獲取注解信息无拗。反射中的Class霜浴,Method,Constructor蓝纲,F(xiàn)ield,Package都繼承了接口AnnotatedElement晌纫,這個(gè)接口主要有以下幾個(gè)方法:
/*判斷是否存在指定的注解*/
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
/*獲取指定的注解*/
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
/*獲取當(dāng)前元素的所有注解*/
Annotation[] getAnnotations();
/*返回直接存在于此元素上的指定的注解税迷,忽略繼承,如果沒有返回null*/
<T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass)
/*返回直接存在于此元素上的所有注解锹漱,忽略繼承*/
Annotation[] getDeclaredAnnotations();
下面看一個(gè)注解的簡(jiǎn)單總和實(shí)例:
@Target(value={ElementType.FIELD})//修飾Filed的注解
@Retention(value = RetentionPolicy.RUNTIME ) //運(yùn)行時(shí)保留
public @interface PName {
String name() default "";
}
@Target(value={ElementType.FIELD})//修飾Filed的注解
@Retention(value = RetentionPolicy.RUNTIME ) //運(yùn)行時(shí)保留
public @interface PAge {
int age() default 0;
}
@Target(value={ElementType.METHOD})//修飾method的注解
@Retention(value = RetentionPolicy.RUNTIME ) //運(yùn)行時(shí)保留
public @interface SayHello {
String content() default "hello";
}
public class People {
@PName(name = "people")
private String name;
@PAge(age = 20)
private int age;
@SayHello(content = "hello people")
public void sayHello(){
System.out.println("hello people");
}
}
public static void main(String[] args) throws NoSuchMethodException {
//獲取people類中所有注解信息
Field[] fields = People.class.getDeclaredFields();
for(Field f : fields){
//遍歷每個(gè)屬性
if(f.isAnnotationPresent(PName.class)){
PName pn = f.getAnnotation(PName.class);
System.out.println(pn.name());
}else{
PAge pa = f.getAnnotation(PAge.class);
System.out.println(pa.age());
}
}
Method md = People.class.getMethod("sayHello");
SayHello sh = md.getAnnotation(SayHello.class);
System.out.println(sh.content());
}
上述的代碼完成了將people類中所有注解信息全部獲取打印的工作箭养。這個(gè)例子可能不能準(zhǔn)確的描述注解在我們程序中的作用(起碼注解不會(huì)用來干這個(gè)),但是在一方面演示了定義到使用注解的過程哥牍,希望對(duì)大家在項(xiàng)目中實(shí)際使用有所啟發(fā)毕泌。
最后,本篇文章結(jié)束了嗅辣,望大家多多留言交流撼泛,相互學(xué)習(xí)。