Java程式設計思想 第二十章:註解
1.前言
註解Annotation又叫元資料,是JDK5中引入的一種以通用格式為程式提供配置資訊的方式。使用註解Annotation可以使元資料寫在程式原始碼中,使得程式碼看起來簡潔,同時編譯器也提供了對註解Annotation的型別檢查,使得在編譯期間就可以排除語法錯誤。
註解使得我們能夠以將由編譯器來測試和驗證的格式,儲存有關程式的額外資訊。註解可以用來生成描述符檔案,甚至是新的類定義,並且有助於減輕編寫"樣板"程式碼的負擔。
1.1 JDK內建的3中Annotation
在JDK5中,內建了3個通用目的的註解Annotation,這三個內建的註解在java.lang包下:
- @Override:
這個註解常用在繼承類或實現介面的子類方法上,表面該方法是子類覆蓋父類的方法,該方法的方法簽名要遵循覆蓋方法的原則:即訪問控制權限必能比父類更嚴格,不能比父類丟擲更多的異常。- @Deprecated:
這個註解告訴編譯器該元素是過時的,即在目前的JDK版本中已經有新的元素代替該元素。- @SuppressWarnings:
該註解關閉編譯器中不合適的警告,即強行壓制編譯器的警告提示。
2. 基本語法
2.1 定義註解
import java.lang.annotation.*;
@Target(ElementType. METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test{}
像這種沒有任何元素的空註解Annotation叫做標記Annotation.在宣告註解的時候往往需要使用@Target,@Retention等註解,這種註解被稱為註解的註解(元資料註解),即是專門用於處理註解Annotation本身的。
@Target註解
用於指示註解所應用的目標程式元素種類,該註解常和ElementType列舉型別一起聯合使用,ElementType列舉提供了java程式中宣告的元素型別如下:
- ANNOTATION_TYPE:註釋型別宣告。
- CONSTRUCTOR:構造方法宣告。
- FIELD:欄位宣告(包括列舉常量)。
- LOCAL_VARIABLE:區域性變數宣告。
- METHOD:方法宣告。
- PACKAGE:包宣告。
- PARAMETER:引數宣告。
- TYPE::類,介面或列舉宣告。
@Retention註解
該註解用於指示所定義的註解型別的註釋在程式宣告週期中得保留範圍,該註解常和RetentionPolicy列舉聯合使用。RetentionPolicy列舉常量定義了註解在程式碼中的保留策略
- CLASS:編譯器把註解記錄在類檔案中,但在執行時JVM不需要保留註解。
- RUNTIME:編譯器把註解記錄在類檔案中,在執行時JVM將保留註解,因此可以通過反射機制讀取註解。
- SOURCE:僅保留在原始碼中,編譯器在編譯時就要丟棄掉該註解。
2. 編寫註解處理器
使用註解的時候,很重要的一部分就是建立註解處理器。Java提供了一個外部工具apt用來解析帶有註解的Java原始碼包。
正常使用註解時,需要在註解中定義元素,用於接收程式設定的值,正常定義註解的例子如下:
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase{
public int id();
public String description() default “no description”;
}
正常定義註解Annotation的方式類似定義介面,id和description是註解UseCase的屬性,而不是方法,註解中不能定義方法只能定義屬性。其中description屬性有預設的值“no description“,即在使用時如果沒有指定description的值,則程式使用其預設值。
上面UseCase註解用於跟蹤方法的測試用例說明,使用上面註解的例子如下:
import java.util.*;
public class PasswordUtils{
@UseCase(id = 47, description = “Passwords must contain at least one numeric”)
public Boolean validatePassword(String password){
return (password.mathes(“\\w*\\d\\w*”));
}
@UseCase(id = 48)
public String encryptPassword(Srring password){
return new StringBuilder(password).reverse().toString();
}
@UseCase(id = 49, description = “New passwords can’t equal previously used ones”)
public Boolean checkForNewPassword(List<String> prevPasswords, String password){
return !prevPasswords.contains(password);
}
}
JDK5中提供了Annotation相關的API,結合使用java的反射機制可以實現自定義的Annotation註解處理器(JDK中也提供了使用APT,Annotationprocess tool方式處理註解,在後面會講解),處理上述Annotation的例子如下:
import java.lang.reflect.*;
import java.util.*;
public class UseCaseTracker{
public static void traceUseCases(List<Integer> useCases, Class<?> clazz){
//獲取指定類中所有宣告的方法
for(Method m : clazz.getDeclaredMethods()){
//獲取方法上指定型別的註解
UseCase uc = m.getAnnotation(UseCase.class);
if(uc != null){
System.out.println(“Found Use Case:” + uc.id() + “ ” + uc.description());
useCases.remove(new Integer(uc.id()));
}
}
for(int i : useCases){
System.out.println(“Warning: Missing use case-” + i);
}
}
public static void main(String[] args){
List<Integer> useCases = new ArrayLis<Integer>();
Collections.addAll(useCases, 47, 48, 49, 50);
trackUseCases(useCases, PasswordUtils.class);
}
}
執行結果:
Found Use Case:47 Passwords must contain at least onenumeric
Found Use Case:48 no description
Found Use Case:49 New Passwords can’t equal previously usedones
Warning: Missing use case-50
這個程式用到了兩個反射的方法:getDeclaredMethods()和m.getAnnotation(UseCase.class).
- getDeclaredMethods() 返回類宣告的方法
- m.getAnnotation(UseCase.class) 返回指定型別的註解物件。如果被註解的方法上沒有該型別的註解,則返回null值。
2.1 Annotation註解元素
Annotation註解中的元素只能是下面的資料型別:
- java的8中基本型別,如int, boolean等等,如果可以自動裝箱和拆箱,則可以使用對應的物件包裝型別。
- String型別。
- Class型別。
- Enums型別。
- Annotation型別。
- 上面型別的陣列。
除了上面這些型別以外,如果在註解中定義其他型別的資料,編譯器將會報錯。
2.2 預設值限制
注意:註解中的元素要麼指定預設值,要麼由使用的類賦值,如果即沒有預設值,使用類也沒有賦值的話,註解元素是不會像普通類成員變數一樣給定預設值,即必須賦值或者顯示指定預設值。預設值例子如下:
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DefaultValue{
public int id() default -1;
public String description() default “”;
}
2.3 生成外部檔案
Enterprise JavaBean這樣的技術,每一個Bean都需要大量的介面和部署來描述檔案,這些都屬於樣板檔案。然而,如果我們使用註解的話,可以將所有資訊都儲存在javaBean中的原始檔中。為此,我們需要一些新的註解,用以定義與Bean相連的資料庫表的名字,以及與Bean屬性管理的列的名字和SQL型別。
import java.lang.annotation.*;
@Target(ElementType.TYPE)//該註解只能應用在類上
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable{//指定資料庫名稱
public String name() default “”;
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints{//資料庫約束
boolean primaryKey() default false;
boolean allowNull() default true;
boolean unique() default false;
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString{//String型別資料
int value() default 0;
String name() default “”;
Constraints constraints() default @Constraints;//註解的屬性元素也是註解
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger{//int型別資料
String name() default “”;
Constraints constraints() default @Constraints;
}