Java系列之註解
原文發於微信公眾號jzman-blog,歡迎關注交流。
Java 註解(Annotation)又稱之為 Java 標註、元資料,是 Java 1.5 之後加入的一種特殊語法,通過註解可以標註 Java 中的類、方法、屬性、引數、包等,可以通過反射原理對這些元資料進行訪問,註解的使用不會影響程式的正常執行,只會對編譯器警告等輔助工具產生影響。
註解功能
- 編譯器可以使用註解來檢測錯誤和取消警告;
- 使用註解可以生成特定程式碼,如 ButtferKnife 使用註解簡化 findViewById等;
- 某些註解可以在執行時進行檢查和操作。
定義註解
註解的定義使用 @interface 作為關鍵字,實際上表示自動繼承了 java.lang.annotation.Annotation 介面,定義格式參考如下:
@元註解 public @interface AnnotationName{ //配置引數(引數型別 引數名稱()) String name() default "hello"; }
配置引數裡面的型別包括基本型別、String、class、列舉以及相關型別的陣列,可以使用 default 設定配置引數的預設值,定義一個註解具體如下:
@Target(value = {ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface TestDefineAnnotation { String[] name() default "test"; }
內建註解
- @Override
- @Deprecated
- @SuppressWarnings
下面是上面三個內建註解的宣告:
//表示當前的方法將覆蓋超類中的方法,編譯時進行格式檢查 @Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { } //表示一個類或者是方法不再建議使用,將其標記為過時,但還是可以使用 @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) @Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings { String[] value(); } //表示關閉不當的編譯器警告資訊 @Documented @Retention(RetentionPolicy.RUNTIME) @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE}) public @interface Deprecated { }
根據對上面三個註解的宣告來看,@SuppressWarnings 中定義了一個數組,這個陣列表示在該註解上具體的目標是那些,如可在 SuppressWarnings 上使用的值,常用的具體如下:
- deprecation:使用了過時的類或方法時的警告
- unused:有未使用的變數時的警告
- unchecked:執行了未檢查的轉換時的警告
- fallthrough:當 switch 程式塊直接通往下一種情況而沒有 break 時的警告
- path:在類路徑、原始檔路徑等中有不存在的路徑時的警告
- serial:當在可序列化的類上缺少serialVersionUID 定義時的警告
- finally :任何 finally 子句不能正常完成時的警告
- all:關於以上所有情況的警告
下面看一個案例,具體如下:
public void test() { long date = Date.parse("2018-04-22"); }
上面的程式碼如果使用 eclipse 等其他 IDE 時會出現兩個警告,一是使用了過時的 API,二是變數 date 賦值後沒有被使用過,警告截圖如下:
當然, IDE 會提示是否新增 SuppressWarnings 來取消這些警告,前文中可以看到註解 @SuppressWarnings 的宣告中需要配置引數,這個引數是一個數組,陣列名稱是 value,可以省略這個名稱, 具體如下:
//不省略 public void test2() { @SuppressWarnings(value= {"deprecation", "unused"}) long date = Date.parse("2018-04-22"); } //省略 public void test2() { @SuppressWarnings({"deprecation", "unused"}) long date = Date.parse("2018-04-22"); }
來張截圖說明一下使用 @SuppressWarnings 的效果,具體如下:
如果只想取消一種警告可以這樣寫,具體如下:
//第一種 public void test2() { @SuppressWarnings(value = {"deprecation"}) long date = Date.parse("2018-04-22"); System.out.println(date); } //第二種 public void test2() { @SuppressWarnings({"deprecation"}) long date = Date.parse("2018-04-22"); System.out.println(date); }
注意:如果在定義註解的配置引數名稱為 value ,那麼可以在配置註解時可以省略 value ,反之,使用其他名稱,則必須採用第一種方式,要指定配置引數名稱。
當然其他註解和 @SuppressWarnings 也比較類似, @Override、@Deprecated 由它們的宣告可知直接使用即可,不需要指定具體目標,在其宣告註解時用到了 @Documented、@Retention、@Target 等,這些用來註解其他註解的特殊註解稱之為元註解,具體請看下文。
元註解
- @Target
- @Retention
- @Documented
- @Inherited
@Target
@Target 用來描述註解的使用範圍,它的宣告如下:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { ElementType[] value(); }
由 @Target 宣告可知使用 @Target 註解必須指定具體的 Java 成員,也就是該註解要使用到哪個位置,具體由列舉 ElementType 中定義,具體如下:
public enum ElementType { TYPE,//類、介面、註解、列舉 FIELD,//屬性(包括列舉常量) METHOD,//方法 PARAMETER,//引數 CONSTRUCTOR,//構造方法 LOCAL_VARIABLE, //區域性變數 ANNOTATION_TYPE,//註解 PACKAGE,//包 /** * 型別註解 * @since 1.8 */ TYPE_PARAMETER, TYPE_USE }
@Retention
@Retention 表示在什麼級別儲存該註解的資訊,它的宣告如下:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { /** * Returns the retention policy. * @return the retention policy */ RetentionPolicy value(); }
由 @Retention 的宣告可知,使用 @Retention 時,必須指定儲存celue(RetentionPolicy),具體值如下:
public enum RetentionPolicy { SOURCE,//在編譯時會被丟棄,僅僅在原始碼中存在 CLASS,//預設策略,執行時就會被丟棄,僅僅在 class 檔案中 RUNTIME//編譯時會將註解資訊記錄到class檔案,執行時任然保留,可以通過反射獲取註解資訊 }
@Documented和 @Inherited 都沒有配置引數,是一種標記註解, @Documented 表示將該註解顯示到使用者文件中, @Inherited 表示該註解只有使用在類上才會有效,而且該註解會被子類繼承。
型別註解
在對元註解的說明中可知從 Java8 開始新增了 型別註解 ,如果在註解 @Target 使用這種註解,表明該註解可以在對應的任何地方使用,如在 @Target 中指定 TYPE_PARAMETER 就可在 自定義型別的宣告處 使用該註解,如在 @Target 中指定 TYPE_USE 就可在任何型別前新增該類之間,主要是方便 Java 開發者使用型別註解和相關外掛(Checker Framework)來檢查來在編譯期檢查執行時的異常。
下面分別定義指定 TYPE_PARAMETER 和 TYPE_USE 的註解,具體如下:
//1. TYPE_PARAMETER @Target(value = {ElementType.TYPE_PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface TypeParameterAnnotation { String value(); } //2. TYPE_USE @Target(value = ElementType.TYPE_USE) @Retention(RetentionPolicy.RUNTIME) public @interface TypeUseAnnotation { }
然後,在下面的案例中使用這兩個註解,具體如下:
/** * 測試註解 * @author jzman */ public class TestAnnotation { //... /** * ElementType.TYPE_PARAMETER * 使用在自定義型別宣告的時候,如註解@TypeParameterAnnotation * @param <T> */ static class TypeAnnotationA<@TypeParameterAnnotation(value="hello") T>{ /** * ElementType.TYPE_USE * 可以使用在任意型別前面(包含TYPE_PARAMETER) */ //建立例項 MyType myType = new @TypeUseAnnotation MyType(); //物件型別 Object obj = (@TypeUseAnnotation Object) myType; //泛型 ArrayList<@TypeUseAnnotation T> list = new ArrayList<>(); //引數中的型別 public String testA(@TypeUseAnnotation String test) { return "Hello"+test; } //列舉 public void testB(@TypeUseAnnotation Color color) { //... } enum Color{ RED, GREEN, BLUE } } static class MyType{} }
其實註解的語法比較簡單,僅僅定義註解對實際開發是沒有幫助的,覺得註解只有在執行時通過反射獲取註解資訊才是最重要的,註解與反射相關的內容會在以後的推文中學習,到此對註解的認識就結束了。
可以選擇關注微信公眾號:jzman-blog 獲取最新更新,一起交流學習!