1. 程式人生 > 實用技巧 >Java註解

Java註解

寫在前面

現在的java主流框架中,註解無處不在,因此我們完全有必要搞明白註解到底是什麼。如何理解註解,它到底是怎麼起作用的。

註解的定義

java是這樣定義註解的:Java 註解用於為 Java 程式碼提供元資料。作為元資料,註解不直接影響你的程式碼執行,但也有一些型別的註解實際上可以用於這一目的。什麼又是元資料的呢?說白了就是描述資料的資料,既然註解被稱為元資料,具有描述作用,那麼我們是不是可以把註解理解成標籤來使用呢。我覺得學習註解,應該著重理解註解的作用是什麼?在日常工作中是如何使用的即可,一般無需我們定義和解釋註解,而註解直譯器,也就是讀取註解的類在框架中多數是隱藏起來的,除非閱讀原始碼,否則根本看不到。
日常開發中新建Java類,我們使用class、interface比較多,而註解和它們一樣,也是一種類的型別,他是用的修飾符為 @interface
格式

public @interface 註解名稱{
    屬性列表;
}

分類
註解可分為JDK內建註解和自定義註解

內建的註解:
Java 定義了一套註解,共有 7 個,3 個在 java.lang 中,剩下 4 個在 java.lang.annotation 中。

作用在程式碼的註解是

  • @Override - 檢查該方法是否是重寫方法。如果發現其父類,或者是引用的介面中並沒有該方法時,會報編譯錯誤。
  • @Deprecated - 標記過時方法。如果使用該方法,會報編譯警告。
  • @SuppressWarnings - 指示編譯器去忽略註解中宣告的警告。

作用在其他註解的註解(或者說 元註解)是:

  • @Retention - 標識這個註解怎麼儲存,是隻在程式碼中,還是編入class檔案中,或者是在執行時可以通過反射訪問。
  • @Documented - 標記這些註解是否包含在使用者文件中。
  • @Target - 標記這個註解應該是哪種 Java 成員。
  • @Inherited - 標記這個註解是繼承於哪個註解類(預設 註解並沒有繼承於任何子類)

從 Java 7 開始,額外添加了 3 個註解:

  • @SafeVarargs - Java 7 開始支援,忽略任何使用引數為泛型變數的方法或建構函式呼叫產生的警告。
  • @FunctionalInterface - Java 8 開始支援,標識一個匿名函式或函式式介面。
  • @Repeatable - Java 8 開始支援,標識某註解可以在同一個宣告上使用多次。

自定義註解:
自定義註解就是自己編寫的註解,而框架中所寫的那些註解也可稱為自定義註解,也是通過被元註解標記而寫出來的註解。

註解的作用

如果說註釋是寫給人看的,那麼註解就是寫給程式看的。它更像一個標籤,貼在一個類、一個方法或者欄位上。它的目的是為當前讀取該註解的程式提供判斷依據。
要牢記,只要用到註解,必然存在三角關係:定義註解,使用註解,註解解析。如下圖:

往往我們要做的是使用註解,而註解的定義跟解析一般框架都幫我們做好了。

註解的使用

1.自定義註解怎麼寫?
在編寫自定義註解之前,我們必須先了解元註解
以下部分摘自簡書(若 | 寒),對元註解的解析做的非常不錯。
上面定義中提到,元註解可以理解為註解的註解,它是作用在註解中,方便我們使用註解實現想要的功能。元註解分別有@Retention、 @Target、 @Document、 @Inherited和@Repeatable(JDK1.8加入)五種。

  • @Retention
    Retention英文意思有保留、保持的意思,它表示註解存在階段是保留在原始碼(編譯期),位元組碼(類載入)或者執行期(JVM中執行)。在@Retention註解中使用列舉RetentionPolicy來表示註解保留時期
    • @Retention(RetentionPolicy.SOURCE),註解僅存在於原始碼中,在class位元組碼檔案中不包含
    • @Retention(RetentionPolicy.CLASS), 預設的保留策略,註解會在class位元組碼檔案中存在,但執行時無法獲得
    • @Retention(RetentionPolicy.RUNTIME), 註解會在class位元組碼檔案中存在,在執行時可以通過反射獲取到

如果我們是自定義註解,則通過前面分析,我們自定義註解如果只存著原始碼中或者位元組碼檔案中就無法發揮作用,而在執行期間能獲取到註解才能實現我們目的,所以自定義註解中肯定是使用 @Retention(RetentionPolicy.RUNTIME)

  • @Target
    Target的英文意思是目標,這也很容易理解,使用@Target元註解表示我們的註解作用的範圍就比較具體了,可以是類,方法,方法引數變數等,同樣也是通過列舉類ElementType表達作用型別
    • @Target(ElementType.TYPE) 作用介面、類、列舉、註解
    • @Target(ElementType.FIELD) 作用屬性欄位、列舉的常量
    • @Target(ElementType.METHOD) 作用方法
    • @Target(ElementType.PARAMETER) 作用方法引數
    • @Target(ElementType.CONSTRUCTOR) 作用建構函式
    • @Target(ElementType.LOCAL_VARIABLE)作用區域性變數
    • @Target(ElementType.ANNOTATION_TYPE)作用於註解(@Retention註解中就使用該屬性)
    • @Target(ElementType.PACKAGE) 作用於包
    • @Target(ElementType.TYPE_PARAMETER) 作用於型別泛型,即泛型方法、泛型類、泛型介面 (jdk1.8加入)
    • @Target(ElementType.TYPE_USE) 型別使用.可以用於標註任意型別除了 class (jdk1.8加入)

一般比較常用的是ElementType.TYPE型別

  • @Documented
    Document的英文意思是文件。它的作用是能夠將註解中的元素包含到 Javadoc 中去。

  • @Inherited
    Inherited的英文意思是繼承,但是這個繼承和我們平時理解的繼承大同小異,一個被@Inherited註解了的註解修飾了一個父類,如果他的子類沒有被其他註解修飾,則它的子類也繼承了父類的註解。

  • @Repeatable
    Repeatable的英文意思是可重複的。顧名思義說明被這個元註解修飾的註解可以同時作用一個物件多次,但是每次作用註解又可以代表不同的含義。

  • 註解屬性型別
    註解屬性型別可以有以下列出的型別

    • 基本資料型別
    • String
    • 列舉型別
    • 註解型別
    • Class型別
    • 以上型別的一維陣列型別

2.如何解析使用註解?
前面說到了註解如何定義,在哪使用等,其他註解的關鍵,是獲取註解中的屬性值,這才是使用註解的真正目的。前面也說到,要想使用自定義的註解,必須要使用@Retention(RetentionPolicy.RUNTIME), 註解會在class位元組碼檔案中存在,在執行時可以通過反射獲取到,而獲取註解的主要手段就是通過反射來獲取。其中有三個主要的方法

 /**是否存在對應 Annotation 物件*/
  public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
        return GenericDeclaration.super.isAnnotationPresent(annotationClass);
    }

 /**獲取 Annotation 物件*/
    public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
        Objects.requireNonNull(annotationClass);

        return (A) annotationData().annotations.get(annotationClass);
    }
 /**獲取所有 Annotation 物件陣列*/   
 public Annotation[] getAnnotations() {
        return AnnotationParser.toArray(annotationData().annotations);
    }    

一個小demo:
MyAnnotation.java

@Target({ElementType.TYPE,ElementType.METHOD})
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value() default "apang";

    int num() default 99;
} 

TestAnnotation.java

@MyAnnotation(value = "註解用在類上")
public class TestAnnotation {

    @MyAnnotation()
    public void test1() {
        //使用預設值
    }

    @MyAnnotation(value = "註解用在方法上",num = 88)
    public void test2() {
        //使用自定義值
    }

}

MyAnnotationProcessor.java

public class MyAnnotationProcessor {
    public static void main(String[] args) throws Exception {
        //獲取類物件
        Class clazz1 = TestAnnotation.class;
        MyAnnotation annotation = TestAnnotation.class.getAnnotation(MyAnnotation.class);
        System.out.println(annotation);
        System.out.println(annotation.value());
        System.out.println(annotation.num());
        Method[] methods = clazz1.getDeclaredMethods();
        for (Method m : methods) {
            Annotation an = m.getAnnotation(MyAnnotation.class);
            System.out.println(an);
            System.out.println(((MyAnnotation) an).value());
            System.out.println(((MyAnnotation) an).num());
        }
    }
}

輸出

>>> @com.apang.annotation.MyAnnotation(value=註解用在類上, num=99)
>>> 註解用在類上
>>> 99
>>> @com.apang.annotation.MyAnnotation(value=註解用在方法上, num=88)
>>> 註解用在方法上
>>> 88
>>> @com.apang.annotation.MyAnnotation(value=apang, num=99)
>>> apang
>>> 99