本博文是對Java中注解相關知識點的簡單總結,若有敘述不清晰或是不准確的地方,希望大家可以指正,謝謝大家:)
我們大家都知道Java代碼中使用注釋是為了向以後閱讀這份代碼的人解釋說明一些事情,注解是注釋的升級版,它可以向編譯器、虛擬機等解釋說明一些事情。比如我們非常熟悉的@Override就是一種元注解,它的作用是告訴編譯器它所注解的方法是重寫父類的方法,這樣編譯器就會去檢查父類是否存在這個方法,以及這個方法的簽名與父類是否相同。
也就是說,注解是描述Java代碼的代碼,它能夠被編譯器解析,注解處理工具在運行時也能夠解析注解。我們在Java源文件中使用注釋,是為了以後我們或他人再來讀這段代碼時,能夠更好地理解它。Javadoc工具可以解析我們在源代碼中為類、方法、變量等添加的描述信息,並根據這些描述信息自動生成一個HTML文檔,這些自動生成的文檔即可作為API幫助文檔。只要我們為類、方法等添加的描述信息符合Javadoc要求的語法,我們就能夠使用Javadoc工具根據我們的描述信息自動生成一個幫助文檔。而注解比java注釋和Javadoc要強大得多,它們三者之間的重大的區別在於,Java注釋和Javadoc描述所發揮的作用僅僅到編譯時就止步了,而注解直到運行時都能夠發揮作用。
我們知道,使用“transient”關鍵字可以告訴編譯器這個域不可序列化。相比於用”transient“這樣的關鍵字修飾一個屬性,注解為我們提供了為類/方法/屬性/變量添加描述信息的更通用的方式,而這些描述信息對於開發者、自動化工具、Java編譯器和Java運行時來說都是有意義的,也就是說他們都能“讀懂”注解信息。”transient“關鍵字是一個修飾符,而注解也是一種修飾符。除了傳遞信息,我們也可以使用注解生成代碼。我們可以使用注解,然後讓注解解析工具來解析它們,以此來生成一些”模板化“的代碼。比如Hibernate、Spring、Axis這些框架大量使用了注解,來避免一些重復的工作。
元注解即用來描述注解的注解,比如以下代碼中我們使用“@Target”元注解來說明MethodInfo這個注解只能應用於對方法進行注解:
@Target(ElementType.METHOD) public @interface MethodInfo { ... }
下面我們來具體介紹一下幾種元注解。
當一個注解類型被@Documented元注解所描述時,那麼無論在哪裡使用這個注解,都會被Javadoc工具文檔化。我們來看一下它的定義:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Documented { }
我們從以上代碼中可以看到,定義注解使用@interface關鍵字,這就好比我們定義類時使用class關鍵字,定義接口時使用interface關鍵字一樣,注解也是一種類型。這個元注解被@Documented修飾,表示它本身也會被文檔化。@Retention元注解的值RetentionPolicy.RUNTIME表示@Documented這個注解能保留到運行時;@Target元注解的值ElementType.ANNOTATION_TYPE表示@Documented這個注解只能夠用來描述注解類型。
表明被修飾的注解類型是自動繼承的。具體解釋如下:若一個注解類型被Inherited元注解所修飾,則當用戶在一個類聲明中查詢該注解類型時,若發現這個類聲明中不包含這個注解類型,則會自動在這個類的父類中查詢相應的注解類型,這個過程會被重復,直到該注解類型被找到或是查找完了Object類還未找到。這個元注解的定義如下:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Inherited { }
我們可以看到這個元注解類型被@Documented所注解,能夠保留到運行時,只能用來描述注解類型。
我們在上面已經見到個這個元注解,它表示一個注解類型會被保留到什麼時候,比如以下代碼表示Developer注解會被保留到運行時:
@Retention(RetentionPolicy.RUNTIME) public @interface Developer { String value(); }
@Retention元注解的定義如下:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { RetentionPolicy value(); }
我們在使用@Retention時,後面括號裡的內容即表示他的取值,從以上定義我們可以看到,取值的類型為RetentionPolicy,這是一個枚舉類型,它可以取以下值:
這個元注解說明了被修飾的注解的應用范圍,也就是被修飾的注解可以用來注解哪些程序元素,它的定義如下:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { ElementType[] value(); }
從以上定義我們可以看到它也會保留到運行時,而且它的取值是為ElementType[]類型(一個數組,意思是可以指定多個值),ElementType是一個枚舉類型,它可以取以下值:
Java本身內建了一些注解,下面我們來介紹一下我們在日常開發中比較常見的注解:@Override、@Deprecated、@SuppressWarnings。相信我們大家或多或少都使用過這三個注解,下面我們一起再重新認識一下它們。
我們先來看一下這個注解類型的定義:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
從它的定義我們可以看到,這個注解可以被用來修飾方法,並且它只在編譯時有效,在編譯後的class文件中便不再存在。這個注解的作用我們大家都不陌生,那就是告訴編譯器被修飾的方法是重寫的父類的中的相同簽名的方法,編譯器會對此做出檢查,若發現父類中不存在這個方法或是存在的方法簽名不同,則會報錯。
這個注解的定義如下:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE}) public @interface Deprecated { }
從它的定義我們可以知道,它會被文檔化,能夠保留到運行時,能夠修飾構造方法、屬性、局部變量、方法、包、參數、類型。這個注解的作用是告訴編譯器被修飾的程序元素已被“廢棄”,不再建議用戶使用。
這個注解我們也比較常用到,先來看下它的定義:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) @Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings { String[] value(); }
它能夠修飾的程序元素包括類型、屬性、方法、參數、構造器、局部變量,只能存活在源碼時,取值為String[]。它的作用是告訴編譯器忽略指定的警告信息,它可以取的值如下所示:
這個注解的使用示例如下:
@SuppressWarning(value={"deprecation", "unchecked"}) public void myMethos() {...}
通過使用以上注解,我們告訴編譯器忽略myMethod方法中由於使用了廢棄的類或方法或是做了未檢查的轉換而產生的警告。
我們可以創建我們自己的注解類型並使用它。請看下面的示例:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Inherited public @interface MethodInfo { String author() default "absfree"; String date(); int version() default 1; }
在自定義注解時,有以下幾點需要我們了解:
我們再把上面提到過的@SuppressWarnings這個注解類型的定義拿出來看一下,這個注解類型是系統為我們定義好的,它的定義如下:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) @Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings { String[] value(); }
我們可以看到,它只定義了一個注解方法value(),它的返回值類型為String[],沒有指定默認返回值。我們使用@SuppressWarnings這個注解所用的語法如下:
@SuppressWarnings(value={"value1", "value2", ...})
也就是在注解類型名稱後的括號內為每個注解方法指定返回值就可以使用這個注解。下面我們來看看怎麼使用我們自定義的注解類型@MethodIngo:
public class AnnotationTest { @MethodInfo(author="absfree", date="20160410") public static void main(String[] args) { System.out.println("Using custom annotation..."); } }
那麼現在問題來了,我們使用的自定義注解對於編譯器或是虛擬機來說是有意義的嗎(編譯器或是虛擬機能讀懂嗎)?顯然我們什麼都不做的話,編譯器或者虛擬機是讀不懂我們的自定義注解的。下面我們來介紹以下注解的解析,讓編譯器或虛擬機能夠讀懂我們的自定義注解。
編譯時注解指的是@Retention的值為CLASS的注解,對於這類注解的解析,我們只需做以下兩件事:
實際上,編譯器在編譯時會自動查找所有繼承自 AbstractProcessor 的類,然後調用他們的 process 方法。因此我們只要做好上面兩件事,編譯器就會主動去解析我們的編譯時注解。現在,我們把上面定義的MethodInfo的Retention改為CLASS,我們就可以按照以下代碼來解析它:
@SupportedAnnotationTypes({ "com.custom.customannotation.MethodInfo" }) public class MethodInfoProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) { HashMap<String, String> map = new HashMap<String, String>(); for (TypeElement typeElement : annotations) { for (Element element : env.getElementsAnnotatedWith(typeElement)) { MethodInfo methodInfo = element.getAnnotation(MethodInfo.class); map.put(element.getEnclosingElement().toString(), methodInfo.author()); } } return false; } }
@SupportedAnnotationTypes注解描述了Processor要解析的注解的名字。process 函數的annotations參數表示 表示待處理的注解集,env表示當前或是之前的運行環境。process函數的返回值表示annotations中的注解是否被這個Processor接受。
首先我們把MethodInfo注解類型中Retention的值改回原來的RUNTIME,接下來我們介紹如何通過反射機制在運行時解析我們的自定義注解類型。
java.lang.reflect包中有一個AnnotatedElement接口,這個接口定義了用於獲取注解信息的幾個方法:
T getAnnotation(Class annotationClass) //返回該程序元素的指定類型的注解,若不存在這個類型的注解則返回null Annotation[] getAnnotations() //返回修飾該程序元素的所有注解 Annotation[] getDeclaredAnnotations() //返回直接修飾該元素的所有注解 boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) //當該程序元素被指定類型注解修飾時,返回true,否則返回false
解析我們上面的自定義注解MethodInfo的相關示例代碼如下(AnnotationParser.java):
public class AnnotationParser { public static void main(String[] args) { try { Class cls = AnnotationTest.class; for (Method method : cls.getMethods()) { MethodInfo methodInfo = method.getAnnotation(MethodInfo.class); if (methodInfo != null) { System.out.println("method name:" + method.getName()); System.out.println("method author:" + methodInfo.author()); System.out.println("method date:" + methodInfo.date()); System.out.println("method version:" + methodInfo.version()); } } } catch (Exception e) { e.printStackTrace(); } } }
運行以上代碼我們可以得到以下輸出:
這說明我們已經成功解析了自定義注解。關於注解有點我們需要明確的是,作為描述代碼本身的一種元數據,注解是一種”被動“的信息。也就是說,必須由編譯器或虛擬機來“主動”解析它,它才能發揮自己的作用。
1. Java Documention
2. 公共技術點之Java注解
3. Java 注解