Java核心技術(shù)點(diǎn)之注解
本博文是對(duì)Java中注解相關(guān)知識(shí)點(diǎn)的簡(jiǎn)單總結(jié),若有敘述不清晰或是不準(zhǔn)確的地方,希望大家可以指正,謝謝大家:)
一、什么是注解
我們大家都知道Java代碼中使用注釋是為了向以后閱讀這份代碼的人解釋說(shuō)明一些事情,注解是注釋的升級(jí)版,它可以向編譯器、虛擬機(jī)等解釋說(shuō)明一些事情。比如我們非常熟悉的@Override就是一種元注解,它的作用是告訴編譯器它所注解的方法是重寫(xiě)父類(lèi)的方法,這樣編譯器就會(huì)去檢查父類(lèi)是否存在這個(gè)方法,以及這個(gè)方法的簽名與父類(lèi)是否相同。
也就是說(shuō),注解是描述Java代碼的代碼,它能夠被編譯器解析,注解處理工具在運(yùn)行時(shí)也能夠解析注解。我們?cè)贘ava源文件中使用注釋?zhuān)菫榱艘院笪覀兓蛩嗽賮?lái)讀這段代碼時(shí),能夠更好地理解它。Javadoc工具可以解析我們?cè)谠创a中為類(lèi)、方法、變量等添加的描述信息,并根據(jù)這些描述信息自動(dòng)生成一個(gè)HTML文檔,這些自動(dòng)生成的文檔即可作為API幫助文檔。只要我們?yōu)轭?lèi)、方法等添加的描述信息符合Javadoc要求的語(yǔ)法,我們就能夠使用Javadoc工具根據(jù)我們的描述信息自動(dòng)生成一個(gè)幫助文檔。而注解比java注釋和Javadoc要強(qiáng)大得多,它們?nèi)咧g的重大的區(qū)別在于,Java注釋和Javadoc描述所發(fā)揮的作用僅僅到編譯時(shí)就止步了,而注解直到運(yùn)行時(shí)都能夠發(fā)揮作用。
我們知道,使用“transient”關(guān)鍵字可以告訴編譯器這個(gè)域不可序列化。相比于用”transient“這樣的關(guān)鍵字修飾一個(gè)屬性,注解為我們提供了為類(lèi)/方法/屬性/變量添加描述信息的更通用的方式,而這些描述信息對(duì)于開(kāi)發(fā)者、自動(dòng)化工具、Java編譯器和Java運(yùn)行時(shí)來(lái)說(shuō)都是有意義的,也就是說(shuō)他們都能“讀懂”注解信息。”transient“關(guān)鍵字是一個(gè)修飾符,而注解也是一種修飾符。除了傳遞信息,我們也可以使用注解生成代碼。我們可以使用注解,然后讓注解解析工具來(lái)解析它們,以此來(lái)生成一些”模板化“的代碼。比如Hibernate、Spring、Axis這些框架大量使用了注解,來(lái)避免一些重復(fù)的工作。
二、元注解
元注解即用來(lái)描述注解的注解,比如以下代碼中我們使用“@Target”元注解來(lái)說(shuō)明MethodInfo這個(gè)注解只能應(yīng)用于對(duì)方法進(jìn)行注解:
|
1
2
3
4
|
@Target(ElementType.METHOD)public @interface MethodInfo { ...} |
下面我們來(lái)具體介紹一下幾種元注解。
1. Documented
當(dāng)一個(gè)注解類(lèi)型被@Documented元注解所描述時(shí),那么無(wú)論在哪里使用這個(gè)注解,都會(huì)被Javadoc工具文檔化。我們來(lái)看一下它的定義:
|
1
2
3
4
5
|
@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)public @interface Documented {} |
我們從以上代碼中可以看到,定義注解使用@interface關(guān)鍵字,這就好比我們定義類(lèi)時(shí)使用class關(guān)鍵字,定義接口時(shí)使用interface關(guān)鍵字一樣,注解也是一種類(lèi)型。這個(gè)元注解被@Documented修飾,表示它本身也會(huì)被文檔化。@Retention元注解的值RetentionPolicy.RUNTIME表示@Documented這個(gè)注解能保留到運(yùn)行時(shí);@Target元注解的值ElementType.ANNOTATION_TYPE表示@Documented這個(gè)注解只能夠用來(lái)描述注解類(lèi)型。
2. Inherited
表明被修飾的注解類(lèi)型是自動(dòng)繼承的。具體解釋如下:若一個(gè)注解類(lèi)型被Inherited元注解所修飾,則當(dāng)用戶(hù)在一個(gè)類(lèi)聲明中查詢(xún)?cè)撟⒔忸?lèi)型時(shí),若發(fā)現(xiàn)這個(gè)類(lèi)聲明中不包含這個(gè)注解類(lèi)型,則會(huì)自動(dòng)在這個(gè)類(lèi)的父類(lèi)中查詢(xún)相應(yīng)的注解類(lèi)型,這個(gè)過(guò)程會(huì)被重復(fù),直到該注解類(lèi)型被找到或是查找完了Object類(lèi)還未找到。這個(gè)元注解的定義如下:
|
1
2
3
4
5
|
@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)public @interface Inherited {} |
我們可以看到這個(gè)元注解類(lèi)型被@Documented所注解,能夠保留到運(yùn)行時(shí),只能用來(lái)描述注解類(lèi)型。
3. Retention
我們?cè)谏厦嬉呀?jīng)見(jiàn)到個(gè)這個(gè)元注解,它表示一個(gè)注解類(lèi)型會(huì)被保留到什么時(shí)候,比如以下代碼表示Developer注解會(huì)被保留到運(yùn)行時(shí):
|
1
2
3
4
|
@Retention(RetentionPolicy.RUNTIME)public @interface Developer { String value();} |
@Retention元注解的定義如下:
|
1
2
3
4
5
6
|
@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)public @interface Retention { RetentionPolicy value();} |
我們?cè)谑褂聾Retention時(shí),后面括號(hào)里的內(nèi)容即表示他的取值,從以上定義我們可以看到,取值的類(lèi)型為RetentionPolicy,這是一個(gè)枚舉類(lèi)型,它可以取以下值:
- SOURCE:表示在編譯時(shí)這個(gè)注解會(huì)被移除,不會(huì)包含在編譯后產(chǎn)生的class文件中;
- CLASS:表示這個(gè)注解會(huì)被包含在class文件中,但在運(yùn)行時(shí)會(huì)被移除;
- RUNTIME:表示這個(gè)注解會(huì)被保留到運(yùn)行時(shí),在運(yùn)行時(shí)可以JVM訪(fǎng)問(wèn)到,我們可以在運(yùn)行時(shí)通過(guò)反射解析這個(gè)注解。
4. Target
這個(gè)元注解說(shuō)明了被修飾的注解的應(yīng)用范圍,也就是被修飾的注解可以用來(lái)注解哪些程序元素,它的定義如下:
|
1
2
3
4
5
6
|
@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)public @interface Target { ElementType[] value();} |
從以上定義我們可以看到它也會(huì)保留到運(yùn)行時(shí),而且它的取值是為ElementType[]類(lèi)型(一個(gè)數(shù)組,意思是可以指定多個(gè)值),ElementType是一個(gè)枚舉類(lèi)型,它可以取以下值:
- TYPE:表示可以用來(lái)注解類(lèi)、接口、注解類(lèi)型或枚舉類(lèi)型;
- PACKAGE:可以用來(lái)注解包;
- PARAMETER:可以用來(lái)注解參數(shù);
- ANNOTATION_TYPE:可以用來(lái)注解 注解類(lèi)型;
- METHOD:可以用來(lái)注解方法;
- FIELD:可以用來(lái)注解屬性(包括枚舉常量);
- CONSTRUCTOR:可以用來(lái)注解構(gòu)造器;
- LOCAL_VARIABLE:可用來(lái)注解局部變量。
三、常見(jiàn)內(nèi)建注解
Java本身內(nèi)建了一些注解,下面我們來(lái)介紹一下我們?cè)谌粘i_(kāi)發(fā)中比較常見(jiàn)的注解:@Override、@Deprecated、@SuppressWarnings。相信我們大家或多或少都使用過(guò)這三個(gè)注解,下面我們一起再重新認(rèn)識(shí)一下它們。
1. @Override注解
我們先來(lái)看一下這個(gè)注解類(lèi)型的定義:
|
1
2
3
4
|
@Target(ElementType.METHOD)@Retention(RetentionPolicy.SOURCE)public @interface Override {} |
從它的定義我們可以看到,這個(gè)注解可以被用來(lái)修飾方法,并且它只在編譯時(shí)有效,在編譯后的class文件中便不再存在。這個(gè)注解的作用我們大家都不陌生,那就是告訴編譯器被修飾的方法是重寫(xiě)的父類(lèi)的中的相同簽名的方法,編譯器會(huì)對(duì)此做出檢查,若發(fā)現(xiàn)父類(lèi)中不存在這個(gè)方法或是存在的方法簽名不同,則會(huì)報(bào)錯(cuò)。
2. @Deprecated
這個(gè)注解的定義如下:
|
1
2
3
4
5
|
@Documented@Retention(RetentionPolicy.RUNTIME)@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})public @interface Deprecated {} |
從它的定義我們可以知道,它會(huì)被文檔化,能夠保留到運(yùn)行時(shí),能夠修飾構(gòu)造方法、屬性、局部變量、方法、包、參數(shù)、類(lèi)型。這個(gè)注解的作用是告訴編譯器被修飾的程序元素已被“廢棄”,不再建議用戶(hù)使用。
3. @SuppressWarnings
這個(gè)注解我們也比較常用到,先來(lái)看下它的定義:
|
1
2
3
4
5
|
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})@Retention(RetentionPolicy.SOURCE)public @interface SuppressWarnings { String[] value();} |
它能夠修飾的程序元素包括類(lèi)型、屬性、方法、參數(shù)、構(gòu)造器、局部變量,只能存活在源碼時(shí),取值為String[]。它的作用是告訴編譯器忽略指定的警告信息,它可以取的值如下所示:
- deprecation:忽略使用了廢棄的類(lèi)或方法時(shí)的警告;
- unchecked:執(zhí)行了未檢查的轉(zhuǎn)換;
- fallthrough:swich語(yǔ)句款中case忘加break從而直接“落入”下一個(gè)case;
- path:類(lèi)路徑或原文件路徑等不存在;
- serial:可序列化的類(lèi)缺少serialVersionUID;
- finally:存在不能正常執(zhí)行的finally子句;
- all:以上所有情況產(chǎn)生的警告均忽略。
這個(gè)注解的使用示例如下:
|
1
2
|
@SuppressWarning(value={"deprecation", "unchecked"})public void myMethos() {...} |
通過(guò)使用以上注解,我們告訴編譯器忽略myMethod方法中由于使用了廢棄的類(lèi)或方法或是做了未檢查的轉(zhuǎn)換而產(chǎn)生的警告。
四、自定義注解
我們可以創(chuàng)建我們自己的注解類(lèi)型并使用它。請(qǐng)看下面的示例:
|
1
2
3
4
5
6
7
8
9
|
@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)@Inheritedpublic @interface MethodInfo { String author() default "absfree"; String date(); int version() default 1;} |
在自定義注解時(shí),有以下幾點(diǎn)需要我們了解:
- 注解類(lèi)型是通過(guò)”@interface“關(guān)鍵字定義的;
- 在”注解體“中,所有的方法均沒(méi)有方法體且只允許public和abstract這兩種修飾符號(hào)(不加修飾符缺省為public),注解方法不允許有throws子句;
- 注解方法的返回值只能為以下幾種:原始數(shù)據(jù)類(lèi)型), String, Class, 枚舉類(lèi)型, 注解和它們的一維數(shù)組,可以為方法指定默認(rèn)返回值。
我們?cè)侔焉厦嫣岬竭^(guò)的@SuppressWarnings這個(gè)注解類(lèi)型的定義拿出來(lái)看一下,這個(gè)注解類(lèi)型是系統(tǒng)為我們定義好的,它的定義如下:
|
1
2
3
4
5
|
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})@Retention(RetentionPolicy.SOURCE)public @interface SuppressWarnings { String[] value();} |
我們可以看到,它只定義了一個(gè)注解方法value(),它的返回值類(lèi)型為String[],沒(méi)有指定默認(rèn)返回值。我們使用@SuppressWarnings這個(gè)注解所用的語(yǔ)法如下:
|
1
|
@SuppressWarnings(value={"value1", "value2", ...}) |
也就是在注解類(lèi)型名稱(chēng)后的括號(hào)內(nèi)為每個(gè)注解方法指定返回值就可以使用這個(gè)注解。下面我們來(lái)看看怎么使用我們自定義的注解類(lèi)型@MethodInfo:
|
1
2
3
4
5
6
|
public class AnnotationTest { @MethodInfo(author="absfree", date="20160410") public static void main(String[] args) { System.out.println("Using custom annotation..."); }} |
那么現(xiàn)在問(wèn)題來(lái)了,我們使用的自定義注解對(duì)于編譯器或是虛擬機(jī)來(lái)說(shuō)是有意義的嗎(編譯器或是虛擬機(jī)能讀懂嗎)?顯然我們什么都不做的話(huà),編譯器或者虛擬機(jī)是讀不懂我們的自定義注解的。下面我們來(lái)介紹以下注解的解析,讓編譯器或虛擬機(jī)能夠讀懂我們的自定義注解。
五、注解的解析
1. 編譯時(shí)解析
編譯時(shí)注解指的是@Retention的值為CLASS的注解,對(duì)于這類(lèi)注解的解析,我們只需做以下兩件事:
- 自定義類(lèi)繼承 AbstractProcessor類(lèi);
- 重寫(xiě)其中的 process 函數(shù)。
實(shí)際上,編譯器在編譯時(shí)會(huì)自動(dòng)查找所有繼承自 AbstractProcessor 的類(lèi),然后調(diào)用他們的 process 方法。因此我們只要做好上面兩件事,編譯器就會(huì)主動(dòng)去解析我們的編譯時(shí)注解?,F(xiàn)在,我們把上面定義的MethodInfo的Retention改為CLASS,我們就可以按照以下代碼來(lái)解析它:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@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 函數(shù)的annotations參數(shù)表示 表示待處理的注解集,env表示當(dāng)前或是之前的運(yùn)行環(huán)境。process函數(shù)的返回值表示annotations中的注解是否被這個(gè)Processor接受。
2. 運(yùn)行時(shí)注解解析
首先我們把MethodInfo注解類(lèi)型中Retention的值改回原來(lái)的RUNTIME,接下來(lái)我們介紹如何通過(guò)反射機(jī)制在運(yùn)行時(shí)解析我們的自定義注解類(lèi)型。
java.lang.reflect包中有一個(gè)AnnotatedElement接口,這個(gè)接口定義了用于獲取注解信息的幾個(gè)方法:
|
1
2
3
4
|
T getAnnotation(Class annotationClass) //返回該程序元素的指定類(lèi)型的注解,若不存在這個(gè)類(lèi)型的注解則返回nullAnnotation[] getAnnotations() //返回修飾該程序元素的所有注解Annotation[] getDeclaredAnnotations() //返回直接修飾該元素的所有注解boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) //當(dāng)該程序元素被指定類(lèi)型注解修飾時(shí),返回true,否則返回false |
解析我們上面的自定義注解MethodInfo的相關(guān)示例代碼如下(AnnotationParser.java):
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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(); } }} |
運(yùn)行以上代碼我們可以得到以下輸出:

這說(shuō)明我們已經(jīng)成功解析了自定義注解。關(guān)于注解有點(diǎn)我們需要明確的是,作為描述代碼本身的一種元數(shù)據(jù),注解是一種”被動(dòng)“的信息。也就是說(shuō),必須由編譯器或虛擬機(jī)來(lái)“主動(dòng)”解析它,它才能發(fā)揮自己的作用。
六、參考資料
1. Java Documention
3. Java 注解



























