關於Java註解(annotation)的簡單理解

學海無涯519發表於2021-02-05

一、什麼是註解?

  從 JDK5 開始,Java增加對後設資料的支援,也就是註解。簡單理解就是程式碼裡的特殊標誌,這些標誌可以在編譯,類載入,執行時被讀取,並執行相應的處理,以便於其他工具補充資訊或者進行部署。

二、為什麼要使用註解?

  註解可以被其他程式(比如:編譯器等)讀取,開發人員可以在不改變原有程式碼和邏輯的情況下在原始碼中嵌入補充資訊。

三、註解的相關概述

3.1 註解的格式

  註解就是以 @XXX 形式在程式碼中存在的,我們還可以為註解新增一些引數值,例如  @SuppressWarnings(value = "unchecked")

3.2 元註解

  元註解就是負責註解其它註解的註解。

  下圖為Java定義的標準的 元註解型別,他們用來對其他的註解進行說明,可以在 Java API 的 java.lang.annotation 包中找到。

  

3.2.1 @Document

官方示例:

// since 1.5
@Documented
@Retention(RUNTIME)
@Target(ANNOTATION_TYPE)
public @interface Documented

官方描述:

  • 如果註釋@Documented出現在註釋型別A的宣告中,那麼元素上的任何@A註釋都被認為是元素公共契約的一部分。
  • 更詳細地說,當使用Documented對註釋型別A進行註釋時,型別A的註釋的存在和值是A註釋的元素的公共契約的一部分。相反,如果註釋型別B沒有被文件化註釋,那麼B註釋的存在和值就不是B註釋元素的公共契約的一部分。
  • 具體地說,如果註釋型別是用Documented註釋的,那麼預設情況下,像javadoc這樣的工具將在其輸出中顯示該型別的註釋,而沒有Documented的註釋型別的註釋將不會顯示。

間而言之:

  如果使用了 @Documented ,就說明此類(或方法、欄位等)的文件化註釋就會被包含在JavaDoc中。

3.2.2 @Inherited

官方示例:

// since 1.5
@Documented
@Retention(RUNTIME)
@Target(ANNOTATION_TYPE)
public @interface Inherited

官方描述:

  • 指示自動繼承批註型別。
  • 如果在註釋型別宣告上存在繼承的元註釋,並且使用者在類宣告上查詢註釋型別,而該類宣告對此型別沒有註釋,則將自動在類的超類中查詢註釋型別。將重複此過程,直到找到此型別的註釋,或到達類層次結構(物件)的頂部。
    如果沒有超類具有此型別的註釋,則查詢將指示所討論的類沒有此類註釋。
  • 請注意,如果註釋型別用於註釋類以外的任何內容,則此元註釋型別無效。還要注意,這個元註釋只會導致註釋從超類繼承;對實現介面的註釋沒有影響。

簡而言之:

  子類可以繼承父類(超類)中的該註解。這個註解指定被他修飾的註解將具有繼承性——如果某個類使用了@XXX,則其子類將自動被@XXX修飾

程式碼示例:

 1 public class test2 {
 2 
 3     public static void main(String[] args) {
 4         Person person = new Person();
 5         Student student = new Student();
 6 
 7         Annotation[] parentAnnotation = person.getClass().getAnnotations();
 8         Annotation[] studentAnnotation = student.getClass().getAnnotations();
 9         for (Annotation annotation : parentAnnotation) {
10             System.out.println(annotation);
11         }
12         System.out.println("---------------------");
13         for (Annotation annotation : studentAnnotation) {
14             System.out.println(annotation);
15         }
16 
17         // 輸出結果
18 //        @com.ruiyicloud.bbfbusiness.demo.annotation.MyAnnotation3()
19 //        ---------------------
20 //        @com.ruiyicloud.bbfbusiness.demo.annotation.MyAnnotation3()
21 
22     }
23 
24 
25 }
26 
27 @Target({ElementType.TYPE})
28 @Retention(RetentionPolicy.RUNTIME)
29 @Inherited
30 @interface MyAnnotation3{
31 
32 }
33 @MyAnnotation3
34 class Person{
35     String name;
36 }
37 
38 class Student extends Person{
39     int age;
40 }

注意:

  如果 MyAnnotation3 註解 去掉 @Inherited,則student.getClass().getAnnotations() 將輸入空。

3.2.3 @Native

官方程式碼:

// since 1.8
@Documented
@Target(FIELD)
@Retention(SOURCE)
public @interface Native

官方描述:

  指示定義常量值的欄位可以從本機程式碼引用。生成本機標頭檔案的工具可以使用該註釋作為提示,以確定是否需要標頭檔案,如果需要,還應該包含哪些宣告。

間而言之:

  使用本地方法,我們可以用java與底層系統的互動,如果使用Java獲取不到我們想要的內容,我們可以選擇使用本地方法。

  使用 @Native 註解修飾變數值的欄位,則表示這個變數可以被原生程式碼引用。

程式碼示例:

  附上Integer的部分原始碼

 1 public final class Integer extends Number
 2         implements Comparable<Integer>, Constable, ConstantDesc {
 3     /**
 4      * A constant holding the minimum value an {@code int} can
 5      * have, -2<sup>31</sup>.
 6      */
 7     @Native public static final int   MIN_VALUE = 0x80000000;
 8 
 9 
10 
11   // 比較值的大小
12     public static int compareUnsigned(int x, int y) {
13         return compare(x + MIN_VALUE, y + MIN_VALUE);
14     }

3.2.4  @Repeatable

官方程式碼:

// since 1.8
@Documented
@Retention(RUNTIME)
@Target(ANNOTATION_TYPE)
public @interface Repeatable

官方描述:

  註釋型別java.lang.annotation註釋.Repeatable用於指示它(meta-)註釋其宣告的註釋型別是可重複的。@Repeatable的值表示可重複註釋型別的包含註釋型別。

間而言之:

  使用@Repeatable這個宣告的註解是可重複的。@Repeatable的值是另一個註解,其可以通過這個另一個註解的值來包含這個可重複的註解。

程式碼示例:

  FooContainer 作用於範圍只能在註解型別上,所以作用於介面上時會報錯

 1 import java.lang.annotation.ElementType;
 2 import java.lang.annotation.Repeatable;
 3 import java.lang.annotation.Target;
 4 
 5 public class test03 {
 6 
 7 }
 8 
 9 
10 @Target({ElementType.TYPE,ElementType.ANNOTATION_TYPE})
11 @Repeatable(FooContainer.class)
12 @interface Foo {}
13 
14 @Target(ElementType.ANNOTATION_TYPE)
15 @interface FooContainer {
16     Foo[] value();
17 }
18 
19 //正確的
20 @Foo @Foo
21 @interface FooContainer1 {
22     Foo[] value();
23 }
24 
25 // 錯誤的
26 @Foo @Foo
27 interface aa {
28     
29 }

注意事項:

  • Foo的保留時間至少與FooContainer一樣長,其中保留用@Retention 註釋顯式或隱式表示。特別:
  • 如果Foo的保留為java.lang.annotation.RetentionPolicy.SOURCE,則FooContainer的保留為java.lang.annotation.RetentionPolicy.SOURCE。
  • 如果Foo的保留值為java.lang.annotation.RetentionPolicy.CLASS,則FooContainer的保留值為java.lang.annotation.RetentionPolicy.CLASS或 java.lang.annotation.RetentionPolicy.SOURCE。
  • 如果保留Foo是java.lang.annotation.RetentionPolicy.RUNTIME,則保留FooContainer是java.lang.annotation.RetentionPolicy.SOURCE, java.lang.annotation.RetentionPolicy.CLASS,或java.lang.annotation.RetentionPolicy.RUNTIME。

3.2.5 @Retention

官方示例:

// since 1.5
@Documented
@Retention(RUNTIME)
@Target(ANNOTATION_TYPE)
public @interface Retention

官方描述:

  • 指示帶批註型別的批註要保留多長時間。如果批註型別宣告上不存在保留批註,則保留策略預設為保留策略.CLASS.
  • 只有當元註釋型別直接用於註釋時,保留元註釋才有效。如果元註釋型別被用作另一個註釋型別中的成員型別,則沒有效果。
列舉常量
列舉常量描述
CLASS
列舉常量將由編譯器記錄在類檔案中,但不需要在執行時由VM保留.
RUNTIME
註釋將由編譯器記錄在類檔案中,並在執行時由VM保留,因此可以反射地讀取它們.
SOURCE
註釋將被編譯器丟棄.

間而言之:

  此註解主要作用為在什麼級別儲存該註解資訊,用於描述註解的宣告週期,需要主要的是 SOURCE < CLASS < RUNTIME 。

3.2.6 @Target

官方示例:

// since 1.5
@Documented
@Retention(RUNTIME)
@Target(ANNOTATION_TYPE)
public @interface Target

官方描述:

  • 指示批註型別適用的上下文。JLS 9.6.4.1中指定了可應用註釋型別的宣告上下文和型別上下文,並在原始碼中用java.lang.annotation註釋.ElementType。
  • 如果@Target元註釋不存在於註釋型別T上,那麼型別T的註釋可以作為除型別引數宣告之外的任何宣告的修飾符來編寫。
  • 如果存在@Target元註釋,編譯器將根據JLS 9.7.4強制執行由ElementType enum常量指示的使用限制。

簡而言之:

  @Target註解主要用於描述註解的使用範圍(例如,新增某些屬性的時候註解可以使用在類上,新增某些屬性的時候註解可以使用在方法上等)

3.3 註解的使用範圍?

ElementType列舉常量
作用範圍
TYPE 類、介面(包括註釋型別)、列舉或記錄
FIELD 欄位宣告(包括列舉常量)
METHOD 方法宣告
PARAMETER 形式引數宣告
CONSTRUCTOR 建構函式宣告
LOCAL_VARIABLE 區域性變數宣告
ANNOTATION_TYPE 註解型別宣告
PACKAGE 包宣告
TYPE_PARAMETER 型別引數宣告(since 8)
TYPE_USE 型別的使用(since 8)
MODULE 模組宣告 (since 9)
RECORD_COMPONENT Java語言的一種預覽功能(since 14)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

3.4 常用的幾個註解

@Override :

  限定父類重寫方法,當子類重寫父類方法時,子類可以加上這個註解,可以確保子類確實重寫了父類的方法,避免出現低階錯誤。

@FunctionalInterface:

  函式式介面,註解保證這個介面只有一個抽象方法,注意這個只能修飾介面。(函式式介面是指 介面中只有一個抽象方法(可以包含多個預設方法或多個static方法),介面體內只能宣告常量欄位和抽象方法,並且被隱式宣告為public,static,final。介面裡面不能有私有的方法或變數。)

@Deprecated:

  標示已過時,這個註解用於表示某個程式元素類,方法等已過時,當其他程式使用已過時的類,方法時編譯器會給出警告。

@SuppressWarning: 

   抑制編譯器警告,被該註解修飾的元素以及該元素的所有子元素取消顯示編譯器警告。

四、自定義註解實戰

  本例建立一個簡單的註解,並在方法上、類上進行使用

@MyAnnotation
public class Main {
    @MyAnnotation
    public static void main(String[] args) {

    }
}

// Target 標識註解可以在什麼地方使用
@Target({ElementType.METHOD,ElementType.TYPE})
// 標識此註解在什麼地方還有效
@Retention(RetentionPolicy.RUNTIME)
// 標識是否將我們的註解生成在JavaDoc中
@Documented
// 子類可以繼承父類的註解
@Inherited
@interface MyAnnotation{

}

五、總結

  總的來說,註解還是比較簡單的。

 

相關文章