Java 如何優雅的使用註解

zeroXuan發表於2019-04-08

什麼是註解

Java註解可以想象成程式碼是具有生命的,註解就是對於程式碼中的某些鮮活的個體貼上一張標籤。簡單的說,註解就如同一張標籤

元註解

元註解是可以註解到註解上的註解,或者說元註解是一種基本註解,但是它能夠應用到其它的註解上面。

其實說白了,元註解也是一張標籤,但是它是一張特殊的標籤,它的作用和目的就是給其他普通的標籤進行解釋說明的

元註解的型別

  • @Documented

    如果使用@Documented修飾Annotation,則表示它可以出現在javadoc中。 定義Annotation時,@Documented可有可無;若沒有定義,則Annotation不會出現在javadoc中。

  • @Retention

    Retention 的英文意為保留期的意思。當 @Retention 應用到一個註解上的時候,它解釋說明了這個註解的的存活時間。定義Annotation時,@Retention可有可無。若沒有@Retention,則@Retention的預設取值是RetentionPolicy.CLASS

    它的取值如下:

    • RetentionPolicy.SOURCE 註解只在原始碼階段保留,在編譯器進行編譯時它將被丟棄並忽視。
    • RetentionPolicy.CLASS 註解只被保留到編譯進行的時候,在載入到 JVM 之前進行丟棄並忽略,即不會載入到JVM中。。
    • RetentionPolicy.RUNTIME 註解可以保留到程式執行的時候,它會被載入進入到 JVM 中,所以在程式執行時可以獲取到它們。
  • @Target

    Target 是目標的意思,@Target 指定了註解運用的地方。 定義Annotation時,@Target可有可無。若有@Target,則該Annotation只能用於它所指定的地方;若沒有@Target,則該Annotation可以用於任何地方。

    你可以這樣理解,當一個註解被 @Target 註解時,這個註解就被限定了運用的場景。

    @Target 有下面的取值

    • ElementType.PACKAGE 可以給一個包進行註解
    • ElementType.ANNOTATION_TYPE 可以給一個註解進行註解
    • ElementType.TYPE 可以給一個型別進行註解,比如類、介面、列舉
    • ElementType.CONSTRUCTOR 可以給構造方法進行註解
    • ElementType.FIELD 可以給屬性進行註解
    • ElementType.METHOD 可以給方法進行註解
    • ElementType.PARAMETER 可以給一個方法內的引數進行註解
    • ElementType.LOCAL_VARIABLE 可以給區域性變數進行註解
  • @Repeatable

    Repeatable 自然是可重複的意思.是指由另一個註解來儲存重複註解,在使用的時候,用儲存註解來擴充套件重複註解。

    Java 如何優雅的使用註解
    建立重複註解Authority時,加上@Repeatable,指向儲存註解Authorities,在使用時候,直接可以重複使用Authority註解。

  • @Inherited

    @Inherited 元註解是一個標記註解,@Inherited闡述了某個被標註的型別是被繼承的。如果一個使用了@Inherited修飾的annotation型別被用於一個class,則這個annotation將被用於該class的子類。

    注意:@Inherited annotation型別是被標註過的class的子類所繼承。類並不從它所實現的介面繼承annotation,方法並不從它所過載的方法繼承annotation。

    當@Inherited annotation型別標註的annotation的Retention是RetentionPolicy.RUNTIME,則反射API增強了這種繼承性。如果我們使用java.lang.reflect去查詢一個@Inherited annotation型別的annotation時,反射程式碼檢查將展開工作:檢查class和其父類,直到發現指定的annotation型別被發現,或者到達類繼承結構的頂層。

註解語法

因為平常開發少見使用註解,導致有不少人認為註解的地位不高。其實Annotationclasssinterface 一樣,註解也是一種型別,只不過它是在 Java SE 5.0 版本中才開始引入的概念。

一個Annotation和一個@Retention中的RetentionPolicy關聯,即每個Annotation,都會有唯一的RetentionPolicy的屬性。

1個Annotation1~n個@Target中的ElementType關聯,即對於每1個Annotation物件,可以有若干個@Target的ElementType屬性值。

示例:


@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
public @interface AnnotationTest {
}
複製程式碼

上面的示例,使用@Documented說明可以出現在Java文件中,@Retention(RetentionPolicy.RUNTIME)說明AnnotationTest可以載入到JVM 中,可以在程式執行的時候的時候通過反射獲取到。@Target(value = {ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})說明AnnotationTest可以用於註解,類和介面,方法,屬性中。

註解的定義

註解通過 @interface關鍵字進行定義。自動繼承了java.lang.annotation.Annotation介面,由編譯程式自動完成其他細節

定義註解時的注意事項

  • 在定義註解時,不能繼承其他的註解或介面
  • @interface用來宣告一個註解,其中的每一個方法實際上相當於一個配置引數方法的名稱就是引數的名稱返回值型別就是引數的型別
  • 可以通過default來宣告引數的預設值。

註解引數支援資料型別:

  1. 所有基本資料型別(int,float,boolean,byte,double,char,long,short)
  2. String型別
  3. Class型別
  4. enum型別
  5. Annotation型別
  6. 以上所有型別的陣列

註解引數的設定:

第一,只能用publicdefault這兩個訪問權修飾.例如,String value();這裡把方法設為defaul預設型別; 

第二,引數成員只能使用註解引數支援的資料型別  

第三,如果只有一個引數成員,最好把引數名稱設為value,後加小括號.

註解元素的預設值:

註解元素必須有確定的值,要麼在定義註解的預設值中指定,要麼在使用註解時指定,非基本型別的註解元素的值不可為null。因此, 使用空字串或0作為預設值是一種常用的做法。這個約束使得處理器很難表現一個元素的存在或缺失的狀態,因為每個註解的宣告中,所有元素都存在,並且都具有相應的值,為了繞開這個約束,我們只能定義一些特殊的值,例如空字串或者負數,一次表示某個元素不存在,在定義註解時,這已經成為一個習慣用法。

定義了註解,並在需要的時候給相關類,類屬性加上註解資訊,如果沒有相應的註解資訊處理流程,註解可以說是沒有實用價值。如何讓註解真真的發揮作用,主要就在於註解處理方法。

如果沒有用來讀取註解的方法和工作,那麼註解也就不會比註釋更有用處了。使用註解的過程中,很重要的一部分就是建立於使用註解處理器。Java SE5擴充套件了反射機制的API,以幫助程式設計師快速的構造自定義註解處理器。

簡單的自定義註解和使用註解例項:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
public @interface AnnotationTest {

    /***
     * 實體預設firstLevelCache屬性為false
     * @return boolean
     */
    boolean firstLevelCache() default false;
    /***
     * 實體預設secondLevelCache屬性為false
     * @return boolean
     */
    boolean secondLevelCache() default true;
    /***
     * 表名預設為空
     * @return String
     */
    String tableName() default "";
    /***
     * 預設以""分割註解
     */
    String split() default "";
}

複製程式碼

它的形式跟介面很類似,只不過前面多了一個@符號。通過上面的語句,就可以建立一個名為AnnotationTest的註解。

註解的使用

@AnnotationTest
public class Test {
}
複製程式碼

上面的程式碼,建立一個類 Test,然後在類定義的地方加上 @AnnotationTest 就可以用 AnnotationTest 註解這個類了。可以簡單的理解為將AnnotationTest 這張標籤貼到 Test 這個類上面。

Java預置的註解

@Deprecated

這個元素是用來標記過時的元素。編譯器在編譯階段遇到這個註解時會發出提醒警告,告訴開發者正在呼叫一個過時的元素比如過時的方法、過時的類、過時的成員變數。

使用如下

public class Hero {
	
	@Deprecated
	public void say(){
		System.out.println("Noting has to say!");
	}
	
	public void speak(){
		System.out.println("I have a dream!");
	}
}
複製程式碼

在IDE中呼叫hero.say()時,say()方法的上面會有方法過時的提醒。

@Override

這個大家應該很熟悉了,提示子類要複寫父類中被 @Override 修飾的方法

@SuppressWarnings

阻止警告的意思。之前說過呼叫被 @Deprecated 註解的方法後,編譯器會警告提醒,而有時候開發者會忽略這種警告,他們可以在呼叫的地方通過 @SuppressWarnings 達到目的。

@SafeVarargs

引數安全型別註解。它的目的是提醒開發者不要用引數做一些不安全的操作,它的存在會阻止編譯器產生 unchecked 這樣的警告。

@SafeVarargs // Not actually safe!
	static void m(List<String>... stringLists) {
	Object[] array = stringLists;
	List<Integer> tmpList = Arrays.asList(42);
	array[0] = tmpList; // Semantically invalid, but compiles without warnings
	String s = stringLists[0].get(0); // Oh no, ClassCastException at runtime!
}
複製程式碼

@FunctionalInterface

函式式介面 (Functional Interface) 就是一個只具有一個方法的普通介面。

例如:

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

複製程式碼

可能有人會疑惑,函式式介面標記有什麼用,這個原因是函式式介面可以很容易轉換為 Lambda 表示式

註解的提取

Java使用Annotation介面來代表程式元素前面的註解,該介面是所有Annotation型別的父介面。除此之外,Java在java.lang.reflect 包下新增了AnnotatedElement介面,該介面代表程式中可以接受註解的程式元素,該介面主要有如下幾個實現類:

  • Class:類定義
  • Constructor:構造器定義
  • Field:類的成員變數定義
  • Method:類的方法定義
  • Package:類的包定義

java.lang.reflect 包下主要包含一些實現反射功能的工具類,實際上,java.lang.reflect 包所有提供的反射API擴充了執行時讀取Annotation資訊的能力。當一個Annotation型別被定義為執行時的Annotation後,該註解才能是執行時可見,當class檔案被裝載時被儲存在class檔案中的Annotation才會被虛擬機器讀取。

AnnotatedElement 介面是所有程式元素(Class、Method和Constructor)的父介面,所以程式通過反射獲取了某個類的AnnotatedElement物件之後,程式就可以呼叫該物件的如下四個個方法來訪問Annotation資訊:   

  • 方法1: T getAnnotation(Class annotationClass): 返回該程式元素上存在的、指定型別的註解,如果該型別註解不存在,則返回null。
  • 方法2:Annotation[] getAnnotations():返回該程式元素上存在的所有註解。
  • 方法3:boolean is AnnotationPresent(Class<?extends Annotation> annotationClass):判斷該程式元素上是否包含指定型別的註解,存在則返回true,否則返回false.
  • 方法4:Annotation[] getDeclaredAnnotations():返回直接存在於此元素上的所有註釋。與此介面中的其他方法不同,該方法將忽略繼承的註釋。(如果沒有註釋直接存在於此元素上,則返回長度為零的一個陣列。)該方法的呼叫者可以隨意修改返回的陣列;這不會對其他呼叫者返回的陣列產生任何影響。

註解與反射

註解通過反射獲取。

  1. 首先可以通過 Class 物件的 isAnnotationPresent() 方法判斷它是否應用了某個註解
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}
複製程式碼
  1. 然後通過 getAnnotation() 方法來獲取 Annotation 物件。
 public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {}
複製程式碼

或者是 getAnnotations() 方法

public Annotation[] getAnnotations() {}
複製程式碼

前一種方法返回指定型別的註解,後一種方法返回註解到這個元素上的所有註解。

使用方法

@TestAnnotation()
public class Test {
	
	public static void main(String[] args) {
		
		boolean hasAnnotation = Test.class.isAnnotationPresent(AnnotationTest.class);
		
		if ( hasAnnotation ) {
			TestAnnotation testAnnotation = Test.class.getAnnotation(AnnotationTest.class);
			
			System.out.println("id:"+testAnnotation.firstLevelCache());
			System.out.println("msg:"+testAnnotation.tableName());
		}

	}

}

複製程式碼

註解的使用場景

註解是一系列後設資料,它提供資料用來解釋程式程式碼,但是註解並非是所解釋的程式碼本身的一部分。註解對於程式碼的執行效果沒有直接影響。

註解用處主要如下:

  • 提供資訊給編譯器

    編譯器可以利用註解來探測錯誤和警告資訊

  • 編譯階段時的處理

    軟體工具可以用來利用註解資訊來生成程式碼、Html文件或者做其它相應處理。

  • 執行時的處理

    某些註解可以在程式執行的時候接受程式碼的提取

相關文章