一 前言
Java注解(Annotation)又稱Java標注,是JDK5.0引入的一種注釋機制。網上也有很多關于Java Annotation的文章,看得使人眼花繚亂,很難理解。
那么到底什么是注解,注解有什么作用,我舉個栗子先,
《奇葩說》第六季已經開播了,這里面我最喜歡的辯手是陳銘,為人師表,談吐舉止得體,他那獨樹一幟的辯論風格,令人聽了不禁十分感動,因為他那是“站在宇宙中心呼喚愛”,以至于他每次出來,“站在宇宙中心呼喚愛”就立刻出現在我的腦海里,然后繼續聽他的辯論,果然如此。“站在宇宙中心呼喚愛”就如同一個標簽,貼在了陳銘身上。
以同樣的方式理解Annotation,Annotation就如同一個標簽一樣,只不過它是貼在了代碼上。單純的Annotation對程序的運行并無影響,就像“站在宇宙中心呼喚愛”這個標簽對于陳銘老師來說,并沒有什么影響,但是“站在宇宙中心呼喚愛”可以用來解釋陳銘老師,同樣Annotation也是對用Annotation注解的程序的解釋,不過是要借助工具,就是上一篇文章介紹的APT。
說了那么多,就是為了理解Annotation,Annotation其實也和class,interface一樣,屬于java的一種類型,只不過在開發中比較少見,為了理解,我們可以假想用標簽來理解。
二 注解的語法
(一) 注解的定義
注解使用@interface關鍵字進行定義,看形式和interface差不多,但是加了一個@符號。
public @interface MyAnnotation {
}
上面的代碼創建了一個MyAnnotation的注解,你可以理解為創建了一個MyAnnotation的標簽,拿著這個標簽,可以給其它的代碼貼上次標簽。
(二)注解的使用
上面創建了一個注解,但是注解怎么使用呢?
@MyAnnotation
public class MyTest {
}
上面的代碼創建了一個注解,然后我們定義了一個類,叫MyTest,在類定義的地方加上@MyAnnotation,就用我們定義的注解,注解到了MyTest類中,你可以簡單理解成,在MyTest類上貼了一張MyAnnotation的標簽。
(三) 元注解
元注解又是什么呢?
要理解上面定義的注解,就必須要理解元注解,元注解是一種基本的注解,它是可以注解到注解上的注解。元注解主要是起對注解解釋說明作用。
元注解有五種,@Retention、@Documented、@Target、@Inherited、@Repeatable 。
① @Retention
Retention意為保留的意思,注解加上@Retention表示注解的存活時間。
它的取值如下:
取值 | 意義 |
---|---|
RetentionPolicy.SOURCE | 注解只在于源碼階段保留,在編譯器進行編譯時它將會被丟棄 |
RetentionPolicy.CLASS | 注解將會保留到編譯階段,但是并不會加載到JVM中 |
RetentionPolicy.RUNTIME | 注解可以保留到程序運行的時候,它會被加載到JVM中 |
下面對我的注解加上元注解,使我們定義的注解保留到程序運行時。
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}
② @Documented
這個元注解很簡單,表示該注解是否可以生成JavaDoc,加上該注解則可以將該注解生成JavaDoc。
③ @Target
Target意為目標的意思,@Target指定了注解運用的地方,即注解的作用域。
@Target的作用域如下所示:
取值 | 作用域 |
---|---|
ElementType.TYPE | 給一個類型進行注解,比如類、接口、枚舉 |
ElementType.FIELD | 可以給一個屬性進行注解 |
ElementType.METHOD | 可以給方法進行注解 |
ElementType.PARAMETER | 可以對一個方法內的參數添加注解 |
ElementType.CONSTRUCTO | 可以對構造函數添加注解 |
ElementType.LOCAL_VARIABLE | 可以對局部變量添加注解 |
ElementType.ANNOTATION_TYPE | 可以對一個注解進行注解 |
ElementType.PACKAGE | 可以對一個包進行注解 |
@Target也可以接受一個數組作為參數,設置多個多個注解的作用域。
下面對我們定義的注解加上@Target元注解,使我們定義的注解可以注解到屬性和方法上。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.METHOD})
public @interface MyAnnotation {
}
④ @Inherited
Inherited意為繼承的意思,默認是false,它并不是說注解本身可以繼承,而是說如果一個類,被一個有@Inherited的注解注解過,如果它的子類沒有被任何注解注解,那么它的子類就繼承了它的注解。說起來比較模糊,show me the code!
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.METHOD,ElementType.TYPE}
@Inherited
public @interface MyAnnotation {
}
@MyAnnotation
public class MyTest {
}
public class MyTestChild extends MyTest{
}
注解MyAnnotation被@Inherited元注解注解了,
類 MyTest,使用MyAnnotation進行注解,
MyTestChild繼承MyTest,并沒有任何注解,MyTestChild也擁有了MyAnnotation注解。
(四) 注解的屬性
注解的屬性,也叫注解的成員變量,注解中只有成員變量,沒有方法。
注解的成員變量在注解的定義中,以 無形參的方法 形式聲明,方法名就是該成員變量的名字,返回值定義了該成員變量的類型。
下面的注解定義了兩個成員變量,分別是int類型的id,和String類型的name。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})
@Inherited
public @interface MyAnnotation {
int id();
String name();
}
在使用我們自定義的注解時,我們應該對屬性進行賦值,賦值的時候,在括號內以value=“”的形式賦值,多個屬性用 , 間隔開。
@MyAnnotation(id=1,name="jack")
public class MyTest {
}
定義注解屬性的時候,可以有默認值,默認值用default關鍵字指定。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})
@Inherited
public @interface MyAnnotation {
int id();
String name() default "frank";
}
(五) Java內置的注解
上面我們自定義了注解,java本身已經提供了一些注解,
- 1 @Override ,這個注解最熟悉了,提示子類中要復寫父類中被@Override修飾的方法。
- 2 @Deprecated,用來標記過時的元素,比如過時的方法,過時的類,過時的成員變量等。
- 3 @SuppressWarnings,阻止警告。
- 4 @SafeVarargs,參數安全類型注解。
- 5 @FunctionalInterface,函數式接口注解,函數式接口就是只有一個方法的接口。
三 注解的提取
上面講了注解的定義和使用,我們在MyTest類加上了我們定義的注解MyAnnotation,
@MyAnnotation(id=1,name="jack")
public class MyTest {
}
那么加上這個注解以后,怎么提取注解的內容呢?
要正確提取注解,需要反射。
注解和反射
注解通過反射獲取。
- 1 通過Class對象的isAnnotationPresent()方法判斷它是否應用了某個注解。
public boolean isAnnotationPresent(@RecentlyNonNull Class<? extends Annotation> annotationClass) {
throw new RuntimeException("Stub!");
}
- 2 通過getAnnotation()方法來獲取Annotation對象。
@Nullable
public <A extends Annotation> A getAnnotation(@NonNull Class<A> annotationClass) {
throw new RuntimeException("Stub!");
}
現在我們來獲取MyTest上的注解。
@MyAnnotation(id=1,name="jack")
public class MyTest {
public static void main(String[] args){
boolean hasAnnotation=MyTest.class.isAnnotationPresent(MyAnnotation.class);
if (hasAnnotation){
MyAnnotation myAnnotation=MyTest.class.getAnnotation(MyAnnotation.class);
System.out.println("id=="+myAnnotation.id());
System.out.println("name=="+myAnnotation.name());
}
}
}
運行結果:
四 注解的使用場景
注解在一下三種時機發揮作用:
- 1 程序開發時,在開發程序時,經常用到@Override @Nullable等注解,這些注解和IDE共同作用是提示代碼編寫過程中的錯誤,這些注解在編譯階段被編譯器丟棄,在class文件中不會出現.
- 2 程序編譯時,軟件工具可以用來利用注解信息來生成代碼、Html文檔或者做其它相應處理。
- 3 程序運行時,當程序運行的時候,我們希望通過獲取標注的注解執行不同的邏輯,如EventBus使用不同線程發送消息。
注解并不是代碼的一部分,當開發者使用了Annotation注解以后,這些Annotation不會自己生效,當使用了注解時,一般通過以下兩種方法處理
- 通過反射自己提取注解
- 由開發者提供相應的代碼來提取并處理這些Annotation信息
這些處理Annotation信息的代碼,我們統稱為APT(Annotation Processing Tool)。想了解APT的可以看下這篇文章。
下一篇文章自己寫個注解處理器
通過寫一個簡單的注解處理器來再次理解注解。