What
Annotation 是何物 ? ** @Override **相信 Java 程序員都不陌生,經常會在一些方法上看到這樣一個符號,這個 ** @Override **便是一個Annotation。如:
@Override
public String toString() {
return super.toString();
}
Annotation:中文譯之為注解,是一種能夠添加到 Java 源代碼中的語法元數據,于 Java SE5中被引入。注解可用來將信息元數據與程序元素進行關聯,可以作用于包、類、接口、方法、域、參數、注解之上。注解為我們在代碼中添加信息提供了一種形式化的方法,使我們可以在稍后某個時刻非常方便的使用這些數據。
Why
注解在一定程度上是在將元數據與源代碼文件結合在一起,而不是保存在外部文檔中的大趨勢下誕生的。注解的優點主要有:
- 注解可以提供用來完整地描述程序所需要的信息;
- 注解可以將一些數據、格式的驗證和測試工作抽取出來交由編譯器;
- 注解可以用來生成描述符文件、新的類定義文件或者一些“樣板”代碼,有助于減輕編寫“樣板”代碼的負擔;
- 使用注解從某種程度上可以提高代碼的可讀性;
注解分類
元注解:即專職用于創建其它注解的注解,Java提供了四種元注解:@Target、@Retention、@Documented 和 @ Inherited
@Target:該注解用于定義注解的作用域,值可以取枚舉類型 ElementType 中的一個或多個,如果未指定Target值,則該注解可以作用于如下所有的域。
- TYPE:類/接口/枚舉/Annotation 聲明
- FIELD:域/枚舉常量聲明
- METHOD:方法聲明
- PARAMETER:參數聲明
- CONSTRUCTOR:構造函數聲明
- LOCAL_VARIABLE:局部變量聲明
- ANNOTATION_TYPE:Annotation 類型聲明
- PACKAGE:包聲明
@Retention:該注解表示需要在什么級別保存該注解信息,可選值為枚舉類型 RetentionPolicy 中的一個:
- SOURCE:注解將被編譯器丟棄,主要起** 標記 **作用,告訴編譯器一些信息,如@Override 和 @SuppressWarnings;
- CLASS:注解在 class 文件中可用,但是會被 JVM 丟棄,可用于編譯時動態處理,如動態生成代碼;
- RUNTIME:注解將會一直保留,因此可以通過反射機制讀取注解信息,@Deprecated 和 @SafeVarargs;
@Documented :將此注解保留在 Javadoc 中;
@Inherited:允許子類繼承父類中的注解。
標準注解:Java內置了四種標準注解:@Override、@SuppressWarnings、@Deprecated 和 @SafeVarargs
@Override:表示當前的方法定義將會覆蓋父類中的方法,如果當前方法簽名與父類中的方法簽名不一致,編譯器將會發出錯誤提示信息;
@SuppressWarnings:關閉不當的編譯器警告信息;
@Deprecated :表示該方法已經被棄用,如果程序中使用到被該注解作用的方法,編譯器會發出警告信息;
@SafeVarargs:該注解是 Java 1.7 新引入的一個注解,表示該注解作用的對象不會對其可變參數進行任何潛在不安全的修改
自定義注解:Java提供的注解數目相對比較少,不過我們可以通過自定義注解的方式創建符合自己程序需要的注解類型。
How
這部分主要介紹如何創建一個新的注解并根據程序需要獲取注解信息進行處理。
定義注解
自定義注解之前,先看看 Java 標準注解是怎樣定義的,從而對注解有個大致印象!
下面兩段代碼分別是 @Override 和 @SuppressWarnings 注解的定義。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE) //使用元注解設置注解作用域、保留策略等
public @interface Override {
}
@Target( { ElementType.TYPE, ElementType.FIELD, ElementType.METHOD,
ElementType.PARAMETER, ElementType.CONSTRUCTOR,
ElementType.LOCAL_VARIABLE })
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
public String[] value(); // 注解配置信息
}
從上面兩個標準注解定義中可以看出,注解的定義有點類似于接口定義,注解也會被編譯成 .class 文件。
** 定義注解的步驟:**
- 使用** @interface ** 就可以定義一個注解,注解名緊隨其后;
- 然后根據程序需要,使用 Java 元注解設置注解的作用域、保留級別、是否可繼承等信息;
- 根據需要,在注解中包含一些元素用于表示一些配置參數,提供必要的注解信息以便于后續對注解進行分析處理。
** 實例 **
@Target(ElementType.METHOD) // 該注解可以作用于方法聲明
@Retention(RetentionPolicy.RUNTIME) // 該注解會一直保留到運行時
@Documented // 將注解信息保留到 Javadoc中
@Inherited // 該注解可以被子類繼承
public @interface Property {
int id(); // int 類型元素 id
String msg() default "default msg"; // 默認值為 “default msg” 的 String 類型元素 msg
// Boolean flag() default false; // Invalide type 'Boolean' for annotaton member
}
上面一段代碼自定義了一個名為 Property 的注解,該注解可以作用于方法聲明,內部包含兩個配置參數 id 和 msg,使用注解時可通過這兩個元素傳遞注解信息。
注解元素
注解中包含的方法聲明即為注解元素,注解元素定義類似于接口中的方法定義,元素的類型即為所需參數的類型,如 int id()表示需要一個int類型值,不過注解元素可以通過使用default關鍵字定義默認值,如果在使用注解時,沒有為設置了默認值的元素提供值,則會使用該元素的默認值。不包含注解元素的注解為** 標記注解 **,這類注解的存在只是為了提供某種標記信息,如@Override注解。
注解元素只能使用如下幾種類型,使用其余類型時,編譯器會給出錯誤提示:
- 所有基本類型(int、char、double等)
- String
- Class
- enum
- Annotation(即嵌套注解)
- 以上類型的數組(注意只能是數組,而不能是容器類型)
使用注解
public class Goods {
@Property(id = 1, msg = "per price")
public void price(){}
@Property(id = 2)
public void type(){}
}
注解通過名值對的形式賦值,具有默認值的注解可以不賦值,從而使用默認值。上面這段代碼在 Goods 類中創建了兩個函數 price() 和 type(),這兩個函數均使用了自定義的 Property 注解,price() 函數為配置參數 id 和 msg 都賦了值,而type()函數只設置了 id 的值,這意味著type()函數的 msg 為默認值“default msg”。
編寫注解處理器
至此已經了解了如何創建和使用注解,然而如果沒有注解處理器對注解信息進行加工處理的話,注解也就不能顯示其強大功能了。Java SE5擴展了反射機制的 API,以幫助程序員構建這類工具。同時,還提供了一個外部工具 apt(Annotation Process Tool)幫助解析帶有注解的 Java源碼。所有注解均可以使用Mirror API 在編譯時便獲取類型信息,但只有@Retention 為 RetentionPolicy.RUNTIME 的注解可以在運行時使用Java反射機制獲取數據。
下面代碼是一個簡單的注解處理器,用于讀取上面的 Goods 類,并使用反射機制在運行時查找我們自定義的注解Property,通過Property注解獲取方法的 id 和 msg。
public class PropertyTracker {
public static void trackerProperty(List<Integer> ids, Class cls){
for (Method method : cls.getDeclaredMethods()){
Property annot = method.getAnnotation(Property.class);
if (annot != null){
print("The Annotation's id is: " + annot.id() + " msg is: " + annot.msg());
ids.remove(new Integer(annot.id()));
}else {
print("This method doesn't has Annotation - Property!");
}
}
for (Integer i : ids){
print("Warning! Missing Property - " + i);
}
}
public static void main(String[] args) {
List<Integer> propertys = new ArrayList<>(5);
Collections.addAll(propertys, 1, 2, 3, 4, 5);
trackerProperty(propertys, Goods.class);
}
}
//Output:
The Annotation's id is: 2 msg is: default msg
The Annotation's id is: 1 msg is: per price
Warning! Missing Property - 3
Warning! Missing Property - 4
Warning! Missing Property - 5
解析:
trackerProperty方法需要一個List 類型參數 ids 和 一個Class類型參數cls,然后它會找出cls中有的Property和缺失的Property。
trackerProperty方法中使用到了兩個反射方法:
getDeclaredMethods():返回一個包含該類中定義的所有方法的數組
getAnnotation():返回實參指定類型的Annotation,如果調用者沒有該類型的注解則返回null值。
通過使用注解對象 annot 的id()和msg()方法獲取注解的id和msg信息。
快速賦值機制
當注解內部只有一個需要賦值的注解元素時,可以將該注解元素名設為value,從而提供一種快捷賦值方式。即使用該注解時,直接在括號內提供注解元素的值即可,而無需使用名值對的方式。
如下所示:
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Person {
int value();
String sex() default "girl";
boolean student() default true;
}
public class Chatlist {
@Person(1)
String per1 = "person1";
@Person(value = 2, sex = "boy", student = false)
String per2 = "person2";
}
注解Person內部包含三個注解元素:value、sex和student,其中sex和student均有默認值,所以value是唯一需要賦值的注解元素,可以使用快速賦值機制。不過當需要賦值的元素不止一個時,則value也需要使用名值對的方式賦值,如per2所示。