Java 如何優雅的使用註解

什麼是註解

Java註解能夠想象成代碼是具備生命的,註解就是對於代碼中的某些鮮活的個體貼上一張標籤。簡單的說,註解就如同一張標籤java

元註解

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

其實說白了,元註解也是一張標籤,可是它是一張特殊的標籤,它的做用和目的就是給其餘普通的標籤進行解釋說明的數組

元註解的類型

  • @Documented安全

    若是使用@Documented修飾Annotation,則表示它能夠出如今javadoc中。 定義Annotation時,@Documented無關緊要;若沒有定義,則Annotation不會出如今javadoc中。bash

  • @Retentionide

    Retention 的英文意爲保留期的意思。當 @Retention 應用到一個註解上的時候,它解釋說明了這個註解的的存活時間。定義Annotation時,@Retention無關緊要。若沒有@Retention,則@Retention的默認取值是RetentionPolicy.CLASS函數

    它的取值以下:工具

    • RetentionPolicy.SOURCE 註解只在源碼階段保留,在編譯器進行編譯時它將被丟棄並忽視。
    • RetentionPolicy.CLASS 註解只被保留到編譯進行的時候,在加載到 JVM 以前進行丟棄並忽略,即不會加載到JVM中。。
    • RetentionPolicy.RUNTIME 註解能夠保留到程序運行的時候,它會被加載進入到 JVM 中,因此在程序運行時能夠獲取到它們。
  • @Targetui

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

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

    @Target 有下面的取值

    • ElementType.PACKAGE 能夠給一個包進行註解
    • ElementType.ANNOTATION_TYPE 能夠給一個註解進行註解
    • ElementType.TYPE 能夠給一個類型進行註解,好比類、接口、枚舉
    • ElementType.CONSTRUCTOR 能夠給構造方法進行註解
    • ElementType.FIELD 能夠給屬性進行註解
    • ElementType.METHOD 能夠給方法進行註解
    • ElementType.PARAMETER 能夠給一個方法內的參數進行註解
    • ElementType.LOCAL_VARIABLE 能夠給局部變量進行註解
  • @Repeatable

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

    建立重複註解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文檔或者作其它相應處理。

  • 運行時的處理

    某些註解能夠在程序運行的時候接受代碼的提取

相關文章
相關標籤/搜索