注解(Annotation)就是一種標簽,可以插入到源代碼中,我們的編譯器可以對他們進行邏輯判斷,或者我們可以自己寫一個工具方法來讀取我們源代碼中的注解信息,從而實現某種操作。需要申明一點,注解不會改變編譯器的編譯方式,也不會改變虛擬機指令執行的順序,它更可以理解為是一種特殊的注釋,本身不會起到任何作用,需要工具方法或者編譯器本身讀取注解的內容繼而控制進行某種操作。本篇文章將從以下幾點詳細的介紹下Java注解的使用:
- 元數據和注解(Annotation)
- 按照參數個數分類注解(標記,單值,完整)
- 按照注解使用途徑分類(標准,元注解,自定義)
- 自定義注解處理器完成讀取注解內容的操作
一、元數據和注解
元數據(meta-data)就是指用來描述數據的數據,它往往是以標簽的形式出現,主要用於描述代碼塊之間的聯系。我們的注解就是一種元數據,根據它所起到的作用,我們可以大致將它分為以下三類:
- 編寫文檔:通過代碼中標識的元數據生成文檔
- 代碼分析:通過代碼中的元數據獲取其中信息內容
- 編譯檢查:通過標記注解可以完成對代碼塊的檢查,例如:@Override,用於檢查格式
二、標准注解(系統自帶)
在我們jdk的java.lang包中定義了三個注解,他們是:@Override,@Deprecated,@SuppressWarnnings。Override這個注解我們經常會使用到,在子類重寫父類方法的時候就會使用到,他會幫助我們校驗格式,確保我們正在定義的方法是在重寫了父類的對應方法。Deprecated注解一般修飾在類或者方法之前,用於表示該方法或者類已經不再推薦使用了。SuppressWarnnings注解主要用於抑制編譯器警告,具體的我們簡單的演示下。
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");
}/*如果你定義的方法不能重寫父類某個方法,要么拼寫錯誤,參數個數,方法名不一樣等,編譯拋出警告*/
}
我們需要注意的是,這里的override注解只能用於修飾方法,不能用於修飾類或者域。
public class Student extends People {
@Deprecated
public void say(){
System.out.println("hello yam");
}
}
//調用過時方法
public static void main(String[] args){
Student s = new Student();
s.say();
}
雖然編譯時拋出了警告,但是程序依然可以正常的運行結束。此注解只是告知用戶被標記的方法或者類已經不再推薦使用,但是你依然是可以使用的。之所以建議不再使用,一定是有了更好的取代物了,如果你一定要在你的項目中使用,等待新的jdk版本發布之后,很可能刪除了這些方法或者類,可能會導致你的項目原先的一些方法或者類無法識別。
@SuppressWarnings("deprecation")
public static void main(String[] args){
Student s = new Student();
s.say();
}
例如,我們可以使用SuppressWarnings注解,阻止彈出過時警告。關於SuppressWarnings的參數主要有以下幾種:
- deprecation:使用了不贊成使用的類或方法時的警告
- unchecked:執行了未檢查的轉換時的警告,例如當使用集合時沒有用泛型 (Generics) 來指定集合保存的類型;
- fallthrough:當 Switch 程序塊直接通往下一種情況而沒有 Break 時的警告;
- path:在類路徑、源文件路徑等中有不存在的路徑時的警告;
- serial:當在可序列化的類上缺少 serialVersionUID 定義時的警告;
- finally:任何 finally 子句不能正常完成時的警告;
- all:關於以上所有情況的警告。
三、元注解
元注解就是用來注解注解的注解。定義可能有點繞,其實元注解是一種注解,他可以加在一般的注解上用於限制該注解的使用范圍,生命周期等。一般在自定義注解時候使用的多。在jdk的中java.lang.annotation包中定義了四個元注解:
- @Target:指定被修飾的注解的作用范圍
- @Retention:指定了被修飾的注解的生命周期
- @Documented:指定了被修飾的注解是可以被例如Javadoc等工具文檔化的
- @Inherited:指定了被修飾的注解修飾程序元素的時候是可以被子類繼承的
我們首先看看@Target的使用:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
這是系統注解Override的定義源代碼,我們看到Target注解中參數ElementType.METHOD表示該注解只能用於修飾方法。使用Target注解限定了Override的修飾范圍只能使方法,不能是類或者域。Target還有一些其他的參數:
- CONSTRUCTOR:用於描述構造器
- FIELD:用於描述域
- LOCAL_VARIABLE:用於描述局部變量
- METHOD:用於描述方法
- PACKAGE:用於描述包
- PARAMETER:用於描述參數
- TYPE:用於描述類、接口(包括注解類型) 或enum聲明
通過上述的參數我們可以在定義一個注解的時候限定他的作用范圍。
下面看看Retention這個元注解,依然以注解Override為例,
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
我們看到Retention中使用了參數RetentionPolicy.SOURCE,這個參數表示該注解只在源代碼中有效,進過編譯之后將會被丟棄。還有一些其他參數:
- SOURCE:在源文件中有效(即源文件保留)
- CLASS:在class文件中有效(即class保留)
- RUNTIME:在運行時有效(即運行時保留)
SOURCE表示編譯器編譯之后的class文件中是不存在這一行注解代碼的,CLASS范圍表示編譯器編譯之后,注解代碼存在於class文件中,但是jvm在加載此class文件的時候會自動忽略掉這一行注解代碼。RUNTIME表示jvm加載class文件的時候會被讀取到內存,也就是運行時保留。
接着使注解Documented,這是一個關於文檔的元注解,被它注解的注解在注解其他方法或者類的時候可以被Javadoc等工具文檔化,對於一般的注解,在Javadoc等工具文檔化類或者方法的時候會丟棄注解內容,使用它就可以使得文檔化的時候依然保存着注解代碼。
//Test是一個被元注解Documented修飾
public class User {
@Test(value = 10,description = "do something")
public void test1() {
}
}
使用Javadoc生成API:
類User中的方法test1方法的頭部是保留着注解的,如果是一般的注解則不會保留。
最后是元注解Inherited,我們知道如果一個普通的注解修飾了一個父類,那么他的子類是不能繼承修飾父類的注解的。
@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上是沒有刪除線的,也就是父類廢棄了,子類依然是正常的。(注解不會被繼承),但是如果我們希望子類能夠繼承父類的某些注解,那么只需要在定義該注解的時候使用我們的元注解Inherited修飾即可。
四、自定義注解
以上我們看到的標准注解,元注解都是jdk中定義好了的,如果我們想要自定義一個自己的注解就需要通過@interface來定義一個全新的注解。
//定義一個注解
public @interface myAnnotion {
}
使用@interface定義一個注解的時候,會自動繼承java.lang.annotation.Annotation接口,以下是其中的一些方法:
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
我們自定義的注解,除了多了個@符號,其他的和定義一個接口是一樣的,所以這些方法我們不用實現。以上我們定義的是一個沒有注解體的一個注解,像這樣的注解我們叫做標記注解,這是表示一種標記,編譯器根據某個類或方法是否具有此標記來判斷是否要添加一些代碼或做一定的檢測操作。例如:@Override注解就是一個標記注解,如果某個方法前被修飾了此注解,編譯器在編譯時會找到父類,判斷對應的方法是否完成了重寫的格式。
下面聲明了一個具有注解體的注解:
public @interface myAnnotion {
String name() default "";
int age();
}
我們說過,聲明注解和聲明接口很是類似,所以注解中的所有參數都必須以抽象方法的形式存在,例如上面一樣。接下來我們看如何使用該注解:
@myAnnotion(name = "walker",age=10)
public class Test_ann {
public static void main(String[] args){
}
}
之前我們說過,注解本身不會起到任何作用,需要配合注解處理器才能發揮一定的作用,自己本身其實更像是一種特殊的注釋。在上例中,我們可以在()中為注解的內部參數賦值,需要注意的是,注解的參數不允許為null,也就是在使用注解的時候,內部的每個參數都是必須要有數值的,要么在定義的時候給賦上默認值(使用default關鍵字),要么在()內顯式的賦值。允許的注解參數類型有:
- 所有基本數據類型(int,float,boolean,byte,double,char,long,short)
- String類型
- Class類型
- enum類型
- Annotation類型
- 以上所有類型的數組
如果我們想要表示注解中某個參數不存在,該怎么辦呢?比如我們用上述自定義的注解去修飾了一個People類,如果此人的age不知道,我們該如何賦值(參數的值不能為null)。我們往往用一些特殊值來標記某個參數不存在的情況,例如我們可以給age賦值-1表示此人年齡不詳,在使用注解處理器讀取的時候發現age等於-1,我們就知道此人年齡不詳。往往字符串類型的參數用""表示參數不存在,整型類型參數使用負數表示參數不存在。
五、使用注解處理器響應注解
我們說過一個注解被定義出來之后,是不能完成任何作用的,如果沒有注解處理器響應的注解和注釋差不多。本小節我們看看如何定義一個注解處理器來對我們自定義的注解進行響應。還有一個前提是:我們的注解處理器實際上也是類,所以它只有在被加載到jvm中才能生效,但是如果我們的注解的生命周期范圍到不了jvm的話,注解處理器也是沒用的。
Java擴充了其反射機制,使得我們可以利用反射來獲取注解信息。反射中的Class,Method,Constructor,Field,Package都繼承了接口AnnotatedElement,這個接口主要有以下幾個方法:
/*判斷是否存在指定的注解*/
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
/*獲取指定的注解*/
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
/*獲取當前元素的所有注解*/
Annotation[] getAnnotations();
/*返回直接存在於此元素上的指定的注解,忽略繼承,如果沒有返回null*/
<T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass)
/*返回直接存在於此元素上的所有注解,忽略繼承*/
Annotation[] getDeclaredAnnotations();
下面看一個注解的簡單總和實例:
@Target(value={ElementType.FIELD})//修飾Filed的注解
@Retention(value = RetentionPolicy.RUNTIME ) //運行時保留
public @interface PName {
String name() default "";
}
@Target(value={ElementType.FIELD})//修飾Filed的注解
@Retention(value = RetentionPolicy.RUNTIME ) //運行時保留
public @interface PAge {
int age() default 0;
}
@Target(value={ElementType.METHOD})//修飾method的注解
@Retention(value = RetentionPolicy.RUNTIME ) //運行時保留
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){
//遍歷每個屬性
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類中所有注解信息全部獲取打印的工作。這個例子可能不能准確的描述注解在我們程序中的作用(起碼注解不會用來干這個),但是在一方面演示了定義到使用注解的過程,希望對大家在項目中實際使用有所啟發。
最后,本篇文章結束了,望大家多多留言交流,相互學習。