Java註解基礎介紹及使用
一、什麼是註解
1、官方定義
Java註解又稱Java標註,是Java語言5.0版本開始支持加入源代碼的特殊語法元數據。Java語言中的類、方法、變量、參數和包等都可以被標註。和Javadoc不同,Java標註可以通過反射獲取標註內容。在編譯器生成類文件時,標註可以被嵌入到字節碼中。Java虛擬機可以保留標註內容,在運行時可以獲取到標註內容。
維基百科對java註解說的還是比較到位了,不理解可以先看看後面,再回來看上面的定義就很清楚了。
2、通俗說法
在Android中,註解的使用無處不在,比如上圖中的 @Override
和 @Nullable
,註解實際就是添加在類、變量、方法、參數等前面的一個修飾符而已,可以理解成一個標籤。
註解和註釋不一樣,註釋只能保留在源碼階段,用來對代碼的解釋或者說明,可以看做就是文本,但是註解不一樣,它可以保留到運行階段,我們可以把註解也理解成代碼,註釋性代碼,它除了提供了對代碼的解釋說明,它還能跟代碼一樣提供功能操作,但它是被動性的,就是它能提供信息,但是需要我們通過其他方式來獲取,比如可以通過其他方式如反射在動態運行階段獲取註解的信息並使用。
3、註解怎麼寫?
我們直接點 @Override
註解進去看看就知道,一個註解是什麼定義的。
這是@Override
註解的寫法,先不管A和B,這個我們後面會說,直接先看D,一個註解,需要使用 @interface
,就跟我們的類需要用class修飾,接口用interface
修飾一樣,只要用@interface
修飾的就是註解。
所以,一個簡單的註解可以如下定義。
public @interface TestAnnotation {}
使用:
4、元註解
再看上圖,還有A@Target
和B@Retention
兩個“註解”,這種用來註解註解的註解我們叫做元註解,如果註解理解成是一個標籤的話,那麼元註解就是比較特殊的標籤,它只能用來對普通標籤進行解釋說明。
元註解有 @Retention、@Documented、@Target、@Inherited、@Repeatable 5 種。
4.1、@Retention
Retention 的英文意爲保留期的意思。當 @Retention 應用到一個註解上的時候,它解釋說明了這個註解的的存活時間。
它有一下三種取值:
- RetentionPolicy.SOURCE :
註解只在源碼階段保留,在編譯器進行編譯時它將被丟棄忽視。
使用場景:這類註解提供信息給編譯器,編譯器可以利用註解來探測錯誤和警告信息,比如我們的
@Override
- RetentionPolicy.CLASS :
註解只被保留到編譯進行的時候,它並不會被加載到 JVM 中。
使用場景:軟件工具可以利用註解信息來生成代碼,Html文檔和做其他相應處理。
- RetentionPolicy.RUNTIME :
註解可以保留到程序運行的時候,它會被加載進入到 JVM 中,所以在程序運行時
使用場景:可以獲取到它們的信息並進行邏輯處理(這也是我們經常用的,會配合反射使用)。
大概示例圖如下:
4.2、@Target
Target
英語就是目標的意思,@Target
指定了註解可以運用的地方,我們前面說了註解可以用在類、方法、變量、參數和包等上面,如果我們沒有使用@Target
元註解對註解進行解釋說明,那麼這個註解可以隨意放在類、方法變量這些地方,我們使用了@Target
後 ,通過指定其可以運用的地方,規定其只能用在我們指定的上。
@Target 參數是一個數組枚舉 ElementType[]
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE, // 接口、類、枚舉、註解
/** Field declaration (includes enum constants) */
FIELD, // 字段、屬性、枚舉的常量
/** Method declaration */
METHOD, // 方法
/** Formal parameter declaration */
PARAMETER,// 方法參數
/** Constructor declaration */
CONSTRUCTOR, //構造函數
/** Local variable declaration */
LOCAL_VARIABLE, //局部變量
/** Annotation type declaration */
ANNOTATION_TYPE, //註解
/** Package declaration */
PACKAGE, //包
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
使用:
@Target(ElementType.FIELD) // 只能註解到一個屬性上。
@Target({ElementType.FIELD,ElementType.METHOD,ElementType.PACKAGE}) // 只能註解到屬性、方法,包上。
4.3、@Documented
標記註解,沒有成員,顧名思義,這個元註解肯定是和文檔有關。它的作用是能夠將註解中的元素包含到 Javadoc 中去。
參考 Annotation深入研究——@Documented註釋使用
4.4、@Inherited
Inherited
是繼承的意思,但是它並不是說註解本身可以繼承,而是說如果一個超類被 @Inherited 註解過的註解進行註解的話,那麼如果它的子類沒有被任何註解應用的話,那麼這個子類就繼承了超類的註解。
下面簡單通過一個Demo來演示一下:
- 首先定義一個註解,並使用
@Inherited
元註解
- 定義一個
Human
類,使用我們自定義註解@Test
- 新建一個
Man
類繼承至Human
類。 - 我們在Man類中通過反射獲取其是否也擁有
@Test
註解
- 從結果可以看出來,在
Man
類也能獲取到@Test
註解和參數,這就是因爲其父類上的註解使用了@Inherited
元註解
4.5、@Repeatable
字面意思是可重複的,說明這個元註解可重複使用,我們看到@Repeatable
註解的變量類型則是對應Annotation(接口)的泛型Class。
-
我們先定義一個重複註解類:
-
再聲明一個容器註解類
-
我們就可以對
@Tag
註解重複使用,可以看到沒有使用@Repeatable
的註解重複使用就會報錯
註解的屬性
通過前面的示例我們可以看到,我們可以爲我們的註解添加屬性,註解的屬性也叫做成員變量。註解只有成員變量,沒有方法。
註解的成員變量在註解的定義中以“無形參的方法”形式來聲明,
其方法名定義了該成員變量的名字,其返回值定義了該成員變量的類型
反射中,通過getDeclaredMethod
來獲取。
在註解中定義屬性時它的類型必須是 8 種基本數據類型外加 類、接口、註解及它們的數組
註解中屬性可以有默認值,默認值需要用 default 關鍵值指定。
前面基本已經演示過了,我們還是簡單演示一下。
定義一個帶有多個成員變量的註解:
/**
* @author : EvanZch
* description:定義多個成員變量註解
**/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestValueAnnotation {
String name() default "EvanZch";
int age();
int[] info();
}
使用:
@TestValueAnnotation(name = "Evan", age = 18, info = {175, 65})
public class AnnotationValueTest {
public static void main(String[] args) {
}
}
註解提取
註解通常與反射一起使用,而反射就是在運行狀態中,對於任意一個類,都能知道這個類的所有屬性及方法,對於任何一個對象,都能調用他的任何一個方法和屬性,這種動態獲取新的及動態調用對象的方法的功能叫做反射.
對反射不熟悉的可以先看看Java反射以及在Android中的使用
註解通過反射獲取。首先可以通過 Class 對象的 isAnnotationPresent() 方法判斷它是否應用了某個註解
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}
然後通過 getAnnotation() 方法來獲取 Annotation 對象。
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {}
或者是 getAnnotations() 方法。
public Annotation[] getAnnotations() {}
前一種方法返回指定類型的註解,後一種方法返回註解到這個元素上的所有註解。
我們通過前面的AnnotationValueTest類演示,獲取AnnotationValueTest類上@TestValueAnnotation
註解的信息。
- 註釋儘可能的詳細了,如果還不清楚可以先去看看反射。
public static void main(String[] args) {
// 一、判斷AnnotationValueTest類上是否有註解
// 1、先拿到AnnotationValueTest的class對象
Class<?> annotationValueTestzz = AnnotationValueTest.class;
// 2、通過其class對象調用isAnnotationPresent進行判斷
boolean isAnnotation = annotationValueTestzz.isAnnotationPresent(TestValueAnnotation.class);
System.out.println("AnnotationValueTest--isAnnotation=" + isAnnotation);
// 如果有註解
if (isAnnotation) {
// 3、有註解拿到這個註解的Annotation對象
TestValueAnnotation annotation = annotationValueTestzz.getAnnotation(TestValueAnnotation.class);
// 4、通過annotation對象獲取其成員變量
int age = annotation.age();
int[] infos = annotation.info();
String name = annotation.name();
System.out.println("AnnotationValueTest--age=" + age + ",info.length=" + infos.length + ",name=" + name);
}
結果:
可以看到,我們成功判斷到AnnotationValueTest
類上的@TestValueAnnotation
註解中的所有屬性信息。
總結
關於註解的使用,在Android中很常見,比如我們butterknife和retrofit庫裏面就大量使用註解來進行操作,我們下一篇,通過一篇註解實戰來寫一個簡易版本的butterknife,下一篇見!