什麼是註解
Java註解可以想象成程式碼是具有生命的,註解就是對於程式碼中的某些鮮活的個體貼上一張標籤。簡單的說,註解就如同一張標籤
。
元註解
元註解是可以註解到註解上的註解,或者說元註解是一種基本註解,但是它能夠應用到其它的註解上面。
其實說白了,元註解也是一張標籤,但是它是一張特殊的標籤,它的作用和目的就是給其他普通的標籤進行解釋說明的
。
元註解的型別
-
@Documented
如果使用
@Documented
修飾Annotation
,則表示它可以出現在javadoc中。 定義Annotation時,@Documented可有可無
;若沒有定義,則Annotation不會出現在javadoc中。 -
@Retention
Retention 的英文意為保留期的意思。當 @Retention 應用到一個註解上的時候,它解釋說明了這個註解的的存活時間。定義Annotation時,
@Retention可有可無
。若沒有@Retention,則@Retention的預設取值是RetentionPolicy.CLASS
。它的取值如下:
- RetentionPolicy.SOURCE 註解只在原始碼階段保留,在編譯器進行編譯時它將被丟棄並忽視。
- RetentionPolicy.CLASS 註解只被保留到編譯進行的時候,在載入到 JVM 之前進行丟棄並忽略,即不會載入到JVM中。。
- RetentionPolicy.RUNTIME 註解可以保留到程式執行的時候,它會被載入進入到 JVM 中,所以在程式執行時可以獲取到它們。
-
@Target
Target 是目標的意思,@Target 指定了註解運用的地方。 定義Annotation時,
@Target可有可無
。若有@Target,則該Annotation只能用於它所指定的地方;若沒有@Target,則該Annotation可以用於任何地方。你可以這樣理解,當一個註解被 @Target 註解時,這個註解就被限定了運用的場景。
@Target 有下面的取值
- ElementType.PACKAGE 可以給一個包進行註解
- ElementType.ANNOTATION_TYPE 可以給一個註解進行註解
- ElementType.TYPE 可以給一個型別進行註解,比如類、介面、列舉
- ElementType.CONSTRUCTOR 可以給構造方法進行註解
- ElementType.FIELD 可以給屬性進行註解
- ElementType.METHOD 可以給方法進行註解
- ElementType.PARAMETER 可以給一個方法內的引數進行註解
- ElementType.LOCAL_VARIABLE 可以給區域性變數進行註解
-
@Repeatable
Repeatable 自然是可重複的意思.是指由另一個註解來儲存重複註解,在使用的時候,用儲存註解來擴充套件重複註解。
建立重複註解Authority時,加上@Repeatable,指向儲存註解Authorities,在使用時候,直接可以重複使用Authority註解。 -
@Inherited
@Inherited 元註解是一個標記註解,@Inherited闡述了某個被標註的型別是被繼承的。如果一個使用了@Inherited修飾的annotation型別被用於一個class,則這個annotation將被用於該class的子類。
注意:@Inherited annotation型別是被標註過的class的子類所繼承。類並不從它所實現的介面繼承annotation,方法並不從它所過載的方法繼承annotation。
當@Inherited annotation型別標註的annotation的Retention是RetentionPolicy.RUNTIME,則反射API增強了這種繼承性。如果我們使用java.lang.reflect去查詢一個@Inherited annotation型別的annotation時,反射程式碼檢查將展開工作:檢查class和其父類,直到發現指定的annotation型別被發現,或者到達類繼承結構的頂層。
註解語法
因為平常開發少見使用註解,導致有不少人認為註解的地位不高。其實Annotation
同 classs
和 interface
一樣,註解也是一種型別,只不過它是在 Java SE 5.0 版本中才開始引入的概念。
一個Annotation
和一個@Retention中的RetentionPolicy
關聯,即每個Annotation
,都會有唯一的RetentionPolicy
的屬性。
1個Annotation
和 1~n個@Target中的ElementType
關聯,即對於每1個Annotation物件
,可以有若干個@Target的ElementType
屬性值。
示例:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
public @interface AnnotationTest {
}
複製程式碼
上面的示例,使用
@Documented
說明可以出現在Java文件中,@Retention(RetentionPolicy.RUNTIME)
說明AnnotationTest可以載入到JVM 中,可以在程式執行的時候的時候通過反射獲取到。@Target(value = {ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})說明AnnotationTest可以用於註解,類和介面,方法,屬性中。
註解的定義
註解通過 @interface
關鍵字進行定義。自動繼承了java.lang.annotation.Annotation
介面,由編譯程式自動完成其他細節
定義註解時的注意事項
- 在定義註解時,
不能繼承其他的註解或介面
。 @interface
用來宣告一個註解,其中的每一個方法實際上相當於一個配置引數
。方法的名稱就是引數的名稱
,返回值型別就是引數的型別
。- 可以通過
default
來宣告引數的預設值。
註解引數支援資料型別:
- 所有基本資料型別(int,float,boolean,byte,double,char,long,short)
- String型別
- Class型別
- enum型別
- Annotation型別
- 以上所有型別的陣列
註解引數的設定:
第一,只能用public
或default
這兩個訪問權修飾.例如,String value();
這裡把方法設為defaul預設型別;
第二,引數成員只能使用註解引數支援的資料型別
第三,如果只有一個引數成員,最好把引數名稱設為value
,後加小括號
.
註解元素的預設值:
註解元素必須有確定的值
,要麼在定義註解的預設值中指定,要麼在使用註解時指定,非基本型別的註解元素的值不可為null。因此, 使用空字串或0作為預設值是一種常用的做法。這個約束使得處理器很難表現一個元素的存在或缺失的狀態,因為每個註解的宣告中,所有元素都存在,並且都具有相應的值,為了繞開這個約束,我們只能定義一些特殊的值,例如空字串或者負數,一次表示某個元素不存在,在定義註解時,這已經成為一個習慣用法。
定義了註解,並在需要的時候給相關類,類屬性加上註解資訊,如果沒有相應的註解資訊處理流程,註解可以說是沒有實用價值。如何讓註解真真的發揮作用,主要就在於註解處理方法。
如果沒有用來讀取註解的方法和工作,那麼註解也就不會比註釋更有用處了。使用註解的過程中,很重要的一部分就是建立於使用註解處理器。Java SE5擴充套件了反射機制的API,以幫助程式設計師快速的構造自定義註解處理器。
簡單的自定義註解和使用註解例項:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
public @interface AnnotationTest {
/***
* 實體預設firstLevelCache屬性為false
* @return boolean
*/
boolean firstLevelCache() default false;
/***
* 實體預設secondLevelCache屬性為false
* @return boolean
*/
boolean secondLevelCache() default true;
/***
* 表名預設為空
* @return String
*/
String tableName() default "";
/***
* 預設以""分割註解
*/
String split() default "";
}
複製程式碼
它的形式跟介面很類似,只不過前面多了一個@
符號。通過上面的語句,就可以建立一個名為AnnotationTest
的註解。
註解的使用
@AnnotationTest
public class Test {
}
複製程式碼
上面的程式碼,建立一個類 Test,
然後在類定義的地方加上 @AnnotationTest
就可以用 AnnotationTest
註解這個類了。可以簡單的理解為將AnnotationTest
這張標籤貼到 Test 這個類上面。
Java預置的註解
@Deprecated
這個元素是用來標記過時的元素
。編譯器在編譯階段遇到這個註解時會發出提醒警告,告訴開發者正在呼叫一個過時的元素比如過時的方法、過時的類、過時的成員變數。
使用如下
public class Hero {
@Deprecated
public void say(){
System.out.println("Noting has to say!");
}
public void speak(){
System.out.println("I have a dream!");
}
}
複製程式碼
在IDE中呼叫hero.say()時,say()方法的上面會有方法過時的提醒。
@Override
這個大家應該很熟悉了,提示子類要複寫父類中被 @Override 修飾的方法
@SuppressWarnings
阻止警告的意思。之前說過呼叫被 @Deprecated 註解的方法後,編譯器會警告提醒,而有時候開發者會忽略這種警告,他們可以在呼叫的地方通過 @SuppressWarnings 達到目的。
@SafeVarargs
引數安全型別註解。它的目的是提醒開發者不要用引數做一些不安全的操作,它的存在會阻止編譯器產生 unchecked 這樣的警告。
@SafeVarargs // Not actually safe!
static void m(List<String>... stringLists) {
Object[] array = stringLists;
List<Integer> tmpList = Arrays.asList(42);
array[0] = tmpList; // Semantically invalid, but compiles without warnings
String s = stringLists[0].get(0); // Oh no, ClassCastException at runtime!
}
複製程式碼
@FunctionalInterface
函式式介面 (Functional Interface) 就是一個只具有一個方法的普通介面。
例如:
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
複製程式碼
可能有人會疑惑,函式式介面標記有什麼用,這個原因是函式式介面可以很容易轉換為 Lambda 表示式
註解的提取
Java使用Annotation介面來代表程式元素前面的註解,該介面是所有Annotation型別的父介面。除此之外,Java在java.lang.reflect 包下新增了AnnotatedElement
介面,該介面代表程式中可以接受註解的程式元素,該介面主要有如下幾個實現類:
- Class:類定義
- Constructor:構造器定義
- Field:類的成員變數定義
- Method:類的方法定義
- Package:類的包定義
java.lang.reflect 包下主要包含一些實現反射功能的工具類,實際上,java.lang.reflect 包所有提供的反射API擴充了執行時讀取Annotation資訊的能力。當一個Annotation型別被定義為執行時的Annotation後,該註解才能是執行時可見,當class檔案被裝載時被儲存在class檔案中的Annotation才會被虛擬機器讀取。
AnnotatedElement
介面是所有程式元素(Class、Method和Constructor)的父介面,所以程式通過反射獲取了某個類的AnnotatedElement物件之後,程式就可以呼叫該物件的如下四個個方法來訪問Annotation資訊:
- 方法1: T getAnnotation(Class annotationClass): 返回該程式元素上存在的、指定型別的註解,如果該型別註解不存在,則返回null。
- 方法2:Annotation[] getAnnotations():返回該程式元素上存在的所有註解。
- 方法3:boolean is AnnotationPresent(Class<?extends Annotation> annotationClass):判斷該程式元素上是否包含指定型別的註解,存在則返回true,否則返回false.
- 方法4:Annotation[] getDeclaredAnnotations():返回直接存在於此元素上的所有註釋。與此介面中的其他方法不同,該方法將忽略繼承的註釋。(如果沒有註釋直接存在於此元素上,則返回長度為零的一個陣列。)該方法的呼叫者可以隨意修改返回的陣列;這不會對其他呼叫者返回的陣列產生任何影響。
註解與反射
註解通過反射獲取。
- 首先可以通過 Class 物件的 isAnnotationPresent() 方法判斷它是否應用了某個註解
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}
複製程式碼
- 然後通過 getAnnotation() 方法來獲取 Annotation 物件。
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {}
複製程式碼
或者是 getAnnotations() 方法
public Annotation[] getAnnotations() {}
複製程式碼
前一種方法返回指定型別的註解,後一種方法返回註解到這個元素上的所有註解。
使用方法
@TestAnnotation()
public class Test {
public static void main(String[] args) {
boolean hasAnnotation = Test.class.isAnnotationPresent(AnnotationTest.class);
if ( hasAnnotation ) {
TestAnnotation testAnnotation = Test.class.getAnnotation(AnnotationTest.class);
System.out.println("id:"+testAnnotation.firstLevelCache());
System.out.println("msg:"+testAnnotation.tableName());
}
}
}
複製程式碼
註解的使用場景
註解是一系列後設資料,它提供資料用來解釋程式程式碼,但是註解並非是所解釋的程式碼本身的一部分。註解對於程式碼的執行效果沒有直接影響。
註解用處主要如下:
- 提供資訊給編譯器
編譯器可以利用註解來探測錯誤和警告資訊
- 編譯階段時的處理
軟體工具可以用來利用註解資訊來生成程式碼、Html文件或者做其它相應處理。
- 執行時的處理
某些註解可以在程式執行的時候接受程式碼的提取