前言
承接注解的上一篇幫你搞懂java注解(一),上一篇把注解是什么、有什么用、什么情況下要用都講了一遍,這篇接著講解下注解該怎么使用。
1、java注解有什么分類(lèi)嗎?
java注解的分類(lèi),可以這么分:分為元注解和普通注解。
元注解是指用于描述普通注解的注解。
我們上一篇文章一直在解析注解是用于描述代碼元素的元數(shù)據(jù)信息,當(dāng)一個(gè)注解被定義好之后,它也屬于一種代碼元素,它也可能有自己的元數(shù)據(jù)信息,有哪些元數(shù)據(jù)信息呢:
例如定義的這個(gè)注解能夠用在哪些代碼元素上;
這個(gè)注解的生命周期能夠保留到哪個(gè)階段:源碼階段,還是字節(jié)碼階段,或者是程序運(yùn)行階段。
這個(gè)注解的這些相關(guān)信息需要被記錄下來(lái),那通過(guò)什么記錄呢,就可以通過(guò)注解來(lái)記錄,因?yàn)槲覀兌家呀?jīng)知道了注解是可以用來(lái)記錄代碼元素的元數(shù)據(jù)信息的,但是這里有一點(diǎn)特殊的地方就是:我們要描述的這個(gè)代碼元素,它恰好也是一個(gè)注解,所以我們現(xiàn)在的做法就是通過(guò)一個(gè)注解來(lái)記錄另一個(gè)注解的元數(shù)據(jù)信息,那這個(gè)用來(lái)描述注解的注解,我們就把它稱(chēng)為元注解。
普通注解是指作用于非注解的代碼元素上的注解,如普通注解可以用來(lái)記錄類(lèi)、接口、方法、屬性、參數(shù)的元數(shù)據(jù)信息,這些代碼元素都不是注解。
所以元注解和普通注解的理解主要是看它的作用對(duì)象是什么:
元注解的作用對(duì)象是注解;
普通注解的作用對(duì)象是類(lèi)、接口、屬性、方法等這些代碼元素。
注解還可以這么分類(lèi):java內(nèi)置注解和自定義注解。
顧名思義,java內(nèi)置注解就是發(fā)布的java軟件包里已經(jīng)定義好的注解,在開(kāi)發(fā)過(guò)程中可以直接拿來(lái)使用的注解;
而自定義注解是指由于java內(nèi)置的注解已經(jīng)不能滿(mǎn)足開(kāi)發(fā)者的需求了,開(kāi)發(fā)者需要自己根據(jù)功能需求定義滿(mǎn)足要求的注解,java提供自定義注解的功能。
java有哪些元注解?
我們可以先找到j(luò)ava.lang.annotation包
小技巧:在idea中輸入代碼java.lang.annotation,然后按住ctrl鍵并點(diǎn)擊annotation就可以快速找到j(luò)dk中的annotation包

如上圖,我們可以看到annotation包下紅框框出的六個(gè)注解,它們都是java的元注解,也就是說(shuō)它們都是用來(lái)修飾注解的,分別是:
@Documented
@Inherited
@Repeatable
@Retention
@Target
它們的含義分別如下:
@Documented
documented有被記錄的意思,用來(lái)修飾注解,表示被它修飾過(guò)的注解都會(huì)被記錄下來(lái);
那記錄到哪里去呢,記錄到j(luò)avadoc文檔中,成為javadoc文檔中api描述的一分子。
那javadoc文檔又是什么呢?
它是一種java程序的幫助文檔,可以用于對(duì)整個(gè)項(xiàng)目、或者項(xiàng)目的某個(gè)模塊、或者項(xiàng)目的某個(gè)package、或者項(xiàng)目的某個(gè)類(lèi)進(jìn)行解釋?zhuān)ㄟ^(guò)查看這個(gè)javadoc幫助文檔,你可以了解到某個(gè)類(lèi)的繼承關(guān)系以及它的方法定義,知道方法的參數(shù)是什么、返回類(lèi)型是什么,知道有哪些方法是從父類(lèi)繼承而來(lái)的,哪些方法是本類(lèi)自己定義的。
如下我定義了一個(gè)注解@Flag:可以看到現(xiàn)在這個(gè)注解@Flag是沒(méi)有使用@Documented注解修飾的
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Flag {
boolean value() default false;
}
然后我又定義了一個(gè)類(lèi):在saying()方法上使用注解@Flag
public class Test {
@Flag(true)
public void saying(){
System.out.println("測(cè)試注解");
}
}
然后我生成了這個(gè)類(lèi)和注解的javadoc幫助文檔,如圖:

可以看到這個(gè)saying()方法上的描述上是沒(méi)有任何關(guān)于@Flag注解的信息的。
注:我是通過(guò)idea生成的javadoc文檔的,關(guān)于如何生成javadoc文檔我參考自:https://blog.csdn.net/weixin_42140580/article/details/89635775
下面我修改下@Flag注解,使用@Documented來(lái)修飾它
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface Flag {
boolean value() default false;
}
然后再次生成javadoc文檔,如圖:

與上次相比,可以看到saying()方法上明顯多出了@Flag注解的信息。
所以,@Documented注解的作用就很明顯了:
在生成javadoc文檔時(shí),被@Documented注解修飾的注解會(huì)被一同記錄到文檔中,如果修飾代碼元素的注解沒(méi)有被@Documented注解修飾,那么在生成javadoc文檔時(shí),它就不會(huì)被記錄進(jìn)去。
@Inherited
Inherited有繼承的意思,用于表示被@Inherited修飾的注解作用于父類(lèi)上,子類(lèi)在繼承父類(lèi)時(shí),可以繼承得到被@Inherited修飾的注解。
這個(gè)繼承不是表示注解繼承注解,關(guān)鍵點(diǎn)是體現(xiàn)在子類(lèi)繼承父類(lèi),且通過(guò)繼承,子類(lèi)可以獲得父類(lèi)上的注解。
子類(lèi)要想不直接使用注解,而是通過(guò)繼承的方式獲得父類(lèi)上的注解,就需要應(yīng)用在父類(lèi)上的注解被@Inherited注解修飾。
關(guān)于@Inherited注解有一些需要注意的地方:
1)被@Inherited注解修飾的注解作用在類(lèi)上,子類(lèi)繼承父類(lèi)時(shí)才會(huì)繼承到父類(lèi)上被@Inherited注解修飾的注解;
2)被@Inherited注解修飾的注解作用在方法或者屬性上,子類(lèi)繼承父類(lèi)時(shí),子類(lèi)的方法或者屬性也不會(huì)繼承得到父類(lèi)方法或?qū)傩陨媳籃Inherited注解修飾的注解;
3)被@Inherited注解修飾的注解作用在接口上,子接口在繼承父接口時(shí),子接口不會(huì)繼承得到父接口上被@Inherited注解修飾的注解;
4)被@Inherited注解修飾的注解作用在接口上,子類(lèi)在實(shí)現(xiàn)接口時(shí),子類(lèi)不會(huì)得到接口上被@Inherited注解修飾的注解;
在子類(lèi)繼承父類(lèi)或者實(shí)現(xiàn)了接口、子接口繼承父接口的情況下,通過(guò)反射獲取到子類(lèi)或者子接口的代碼元素(如Method、Field等),在子類(lèi)或者子接口沒(méi)有直接使用注解的情況下,判斷這些代碼元素上是否存在與父類(lèi)或者父接口上一樣的注解,就可以驗(yàn)證得到上述結(jié)論。
@Repeatable
先來(lái)看一張圖,這是@Repeatable注解的定義。
從源碼中可以知道這個(gè)元注解是jdk1.8才引入的,value()方法的返回值類(lèi)型是Class類(lèi)型。

Repeatable的意思是可重復(fù)的,它作為一個(gè)元注解,表示被它修飾的注解可以在某個(gè)代碼元素上重復(fù)使用多次。這個(gè)應(yīng)該怎么理解呢?
比如我現(xiàn)在定義一個(gè)注解,但是這個(gè)注解我不使用@Repeatable元注解修飾:
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
public @interface IsRatable {
String name() default "";
}
我如果想在同一個(gè)代碼元素上重復(fù)多次使用這個(gè)注解的話會(huì)怎么樣?會(huì)不會(huì)有問(wèn)題呢?比如我現(xiàn)在要在類(lèi)上重復(fù)使用這個(gè)注解,請(qǐng)看下圖:

可以看到注解報(bào)錯(cuò)了,同一個(gè)注解在同一個(gè)代碼元素上重復(fù)使用確實(shí)有問(wèn)題。
那如果我確實(shí)希望能夠?qū)崿F(xiàn)同一個(gè)注解可以在同一個(gè)代碼元素上重復(fù)使用,這個(gè)應(yīng)該怎么解決呢?
這時(shí)候就需要用上jdk1.8新引入的@Repeatable注解了。
在開(kāi)始寫(xiě)案例之前我們先考慮一個(gè)問(wèn)題:我們?yōu)槭裁葱枰粋€(gè)注解可以被重復(fù)使用?
同一個(gè)注解在同一個(gè)代碼元素上被重復(fù)使用,是希望后一次的會(huì)覆蓋前一次的嗎?如果是希望覆蓋的話,那我為什么要重復(fù)使用呢?我單獨(dú)使用最新的那個(gè)不就行了嗎。根據(jù)這個(gè)思維來(lái)考慮,顯然這不是重復(fù)使用的目的。
重復(fù)使用的目的不是覆蓋,那會(huì)不會(huì)是每次使用注解所存儲(chǔ)的數(shù)據(jù)都會(huì)被記錄下來(lái)放到某個(gè)容器里面存儲(chǔ)起來(lái)呢?也就是說(shuō)重復(fù)使用注解的目的是不是為了通過(guò)注解存儲(chǔ)多個(gè)不同的值呢?嗯,這么一想還真有可能。
例如,一個(gè)人可能就會(huì)有多重身份,他可能是某人的爸爸,可能是某人的丈夫,還可能是某人的兄弟...
那如果通過(guò)注解來(lái)存儲(chǔ)一個(gè)人的身份的話,這個(gè)人只有一個(gè)身份,那么注解就只使用一次,只存儲(chǔ)一個(gè)值,如果這個(gè)人有多個(gè)身份的話,那么就需要重復(fù)使用多次注解,通過(guò)多次使用,傳入多個(gè)不同的身份信息并存儲(chǔ)起來(lái)。
我們通過(guò)一個(gè)例子具體看看,具體理解下:
首先定義一個(gè)可存儲(chǔ)角色信息的注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
//使用了@Repeatable元注解修飾@Role注解,并且@Repeatable注解傳入的值是Roles.class,
//因?yàn)槲覀兩厦嬲f(shuō)過(guò)了@Repeatable注解的返回值類(lèi)型是Class類(lèi)型,表示它存儲(chǔ)的值是Class類(lèi)型的,所以這里傳入的是字節(jié)碼對(duì)象Roles.class。
//那為什么傳入的是Roles.class而不是Role.class呢?關(guān)于這個(gè)我們?cè)谙旅孢M(jìn)行解析。
@Repeatable(Roles.class)
public @interface Role{
String value() default "";
}
這樣我們就定義了一個(gè)普通注解@Role,并且使用了@Repeatable元注解進(jìn)行修飾。
且元注解只有一個(gè)value()方法。
根據(jù)我們上面的分析過(guò)程,某個(gè)注解要在同一個(gè)代碼元素上重復(fù)多次使用,其目的是為了通過(guò)這個(gè)注解傳入多個(gè)值并存儲(chǔ)起來(lái)。對(duì)于多個(gè)元素的存儲(chǔ),我們是不是需要依賴(lài)容器,比如數(shù)組或者集合等。
注解也是基于這種思想來(lái)設(shè)計(jì)多個(gè)元素的存儲(chǔ)的:我們要存儲(chǔ)多個(gè)元素,我們就需要一個(gè)創(chuàng)建一個(gè)容器來(lái)存儲(chǔ)它們。
所以就出現(xiàn)了container annotation,即容器注解這個(gè)概念。
顧名思義,容器注解是指把注解當(dāng)作一個(gè)容器來(lái)看代,它可以存儲(chǔ)多個(gè)元素,但是它存儲(chǔ)的元素比較特殊--它存儲(chǔ)的是注解。
所以,理解容器注解需要理解兩點(diǎn):
1)首先它是一個(gè)注解
2)它存儲(chǔ)的元素也是注解
所以,總結(jié)一下容器注解就是用來(lái)存儲(chǔ)注解的注解。
然后我們來(lái)看看一個(gè)容器注解該怎么定義,還是接著上面人有多個(gè)身份的案例來(lái)設(shè)計(jì),定義一個(gè)容器注解@roles:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
public @interface roles{
//定義了一個(gè)value()方法,其返回值類(lèi)型為一個(gè)Role[]數(shù)組類(lèi)型,
//根據(jù)前面的定義我們知道,Role也是一個(gè)注解,所以這里在一個(gè)注解里面通過(guò)一個(gè)數(shù)組容器來(lái)
//存儲(chǔ)多個(gè)注解信息,所以這就是注解容器。
Role[] value() default {};
}
有些人看到這里可能會(huì)有疑問(wèn)了,嗯?返回值類(lèi)型是數(shù)組的就是容器注解?那下面定義的這個(gè)也是容器注解了???
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Types {
String[] value() default {};
}
這還真不是,如果不信,咱們可以做個(gè)實(shí)驗(yàn)。
假設(shè)這是容器注解,那么肯定可以通過(guò)Repeatable注解應(yīng)用在其他注解上,我們現(xiàn)在就來(lái)試一下能不能成功應(yīng)用在其他注解上,我們定義一個(gè)@Type注解,看下圖:

所以,容器注解并不是方法返回值是一個(gè)數(shù)組類(lèi)型就行了,得滿(mǎn)足前面說(shuō)過(guò)的兩個(gè)特點(diǎn):
1)首先它本身是一個(gè)注解
2)方法的返回值是注解數(shù)組類(lèi)型
好了,至此我們可以測(cè)試一下我們前面提到的人的身份的案例了,測(cè)試?yán)尤缦?:
定義一個(gè)Person類(lèi),然后在類(lèi)上重復(fù)多次使用被@Repeatable注解修飾的注解@Role,為Person指定多重身份。
@Role("father")
@Role("son")
@Role("brother")
public class Person {
public static void main(String[] args) {
Roles annotation = Person.class.getAnnotation(Roles.class);
Role[] roles = annotation.value();
for (Role role : roles){
String roleName = role.value();
System.out.println(roleName);
}
}
}
這回是沒(méi)有報(bào)錯(cuò)的了,可以使用成功,看運(yùn)行結(jié)果,把傳入的多個(gè)身份都獲取到了:

**容器注解定義的注意事項(xiàng):
1)容器注解的定義里必須含有value()方法

2)容器注解里value()方法的返回值類(lèi)型必須為注解數(shù)組類(lèi)型

@Retention
Retention有保留之意,表示被@Retention修飾的注解會(huì)被保留到哪個(gè)階段,這里的階段主要有3大階段:源碼階段、字節(jié)碼階段、運(yùn)行時(shí)階段。
Retention的取值有3種,對(duì)應(yīng)上述的3個(gè)階段,它們?cè)谝粋€(gè)枚舉聲明RetentionPolicy中被定義好了,分別是SOURCE、CLASS、RUNTIME。
public enum RetentionPolicy {
/**
* 表示注解保留階段是源碼階段,即只會(huì)在.java代碼中存在
*/
SOURCE,
/**
* 表示注解保留階段延續(xù)到字節(jié)碼階段,即會(huì)存在于.java代碼以及.class文件中
*/
CLASS,
/**
* 表示注解保留階段延續(xù)到運(yùn)行期間,即注解不僅會(huì)在.java代碼以及.class文件中保留,而且在程序運(yùn)行期間還會(huì)被加載到j(luò)vm內(nèi)存中,
* 因此可以做到在程序運(yùn)行期間獲取注解的信息
*/
RUNTIME
}
注意:如果開(kāi)發(fā)者在定義注解時(shí),如果不通過(guò)@Retention元注解來(lái)指定注解的保留階段,那么默認(rèn)情況下(也就是不使用@Retention元注解的情況),注解的保留階段是到字節(jié)碼階段的。
也就是默認(rèn)情況下,.java代碼和.class文件中都會(huì)保留注解信息,但是運(yùn)行時(shí)不會(huì)加到到內(nèi)存了。
你可以自定義一個(gè)注解,不使用元注解@Retention修飾,然后運(yùn)行程序,你看看能不能獲取到代碼元素上的注解信息,答案是肯定獲取不到的。
@Target
Target有目標(biāo)之意,表示被@Target注解修飾的注解到時(shí)候可以作用于哪些目標(biāo)代碼元素的。
例如現(xiàn)在定義一個(gè)注解@First,通過(guò)@Target來(lái)指定這個(gè)注解@First可以作用于類(lèi)、接口、方法和屬性:
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
public @interface First {
String value() default "";
}
可以看到@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})的傳的參數(shù)是一個(gè)數(shù)組,數(shù)組中指定了3個(gè)元素,分別是:ElementType.TYPE、ElementType.METHOD、ElementType.FIELD。
表示我現(xiàn)在定義的這個(gè)@First注解最終作用的代碼元素只能是類(lèi)、接口、枚舉,或者方法,或者字段這幾種代碼元素,想要作用于其他的代碼元素如方法參數(shù)PARAMETER上則會(huì)報(bào)錯(cuò),因?yàn)锧Target沒(méi)有指定@First注解可以用于方法參數(shù)上。
通過(guò)一個(gè)例子闡明@Target這個(gè)元注解的作用是約束普通注解的作用范圍的,一個(gè)普通注解可以作用于哪些代碼元素,取決于@Target元注解中指定的類(lèi)型。
那我們的代碼元素到底有哪些呢,或者說(shuō)@Target的取值到底有哪些呢?它也在枚舉聲明ElementType中被明確定義好了,如下:
public enum ElementType {
/**
* 這個(gè)類(lèi)型表示的是類(lèi)、接口或者枚舉,
* 意思是,如果@Target注解指定的值是TYPE,那么被@Target注解修飾的注解就可以作用于類(lèi)、接口或者枚舉聲明上
*/
TYPE,
/** 這個(gè)類(lèi)型表示的是屬性,
* 意思是,如果@Target注解指定的值是FIELD,那么被@Target注解修飾的注解就可以作用于屬性上
*/
FIELD,
/** 這個(gè)類(lèi)型表示的是方法,
* 意思是,如果@Target注解指定的值是METHOD,那么被@Target注解修飾的注解就可以作用于方法上
*/
METHOD,
/** 這個(gè)類(lèi)型表示的是方法參數(shù),
* 意思是,如果@Target注解指定的值是PARAMETER,那么被@Target注解修飾的注解就可以作用于方法參數(shù)上
*/
PARAMETER,
/** 這個(gè)類(lèi)型表示的是構(gòu)造方法,
* 意思是,如果@Target注解指定的值是CONSTRUCTOR,那么被@Target注解修飾的注解就可以作用于構(gòu)造方法上
*/
CONSTRUCTOR,
/** 這個(gè)類(lèi)型表示的是局部變量,
* 意思是,如果@Target注解指定的值是LOCAL_VARIABLE,那么被@Target注解修飾的注解就可以作用于LOCAL_VARIABLE上,
* 哪些局部變量呢?比如在方法體內(nèi)部定義的變量
*/
LOCAL_VARIABLE,
/** 這個(gè)類(lèi)型表示的是注解,
* 意思是,如果@Target注解指定的值是ANNOTATION_TYPE,那么被@Target注解修飾的注解就可以作用于注解上,
* 注意請(qǐng)仔細(xì)看這里,這個(gè)說(shuō)明如果通過(guò)@Target指定的類(lèi)型是ANNOTATION_TYPE的話,那么當(dāng)前定義的這個(gè)注解就是個(gè)元注解,
* 它到時(shí)候是要作用于普通注解上的
*/
ANNOTATION_TYPE,
/** 這個(gè)類(lèi)型表示的是java包,
* 意思是,如果@Target注解指定的值是PACKAGE,那么被@Target注解修飾的注解就可以作用于java包上
*/
PACKAGE,
/**
* TYPE_PARAMETER的意思是類(lèi)型參數(shù),類(lèi)型參數(shù)是啥呢?它就是泛型。
* 泛型把類(lèi)型當(dāng)作一種參數(shù)來(lái)看代。
* 如果@Target注解指定的值是TYPE_PARAMETER,那么被@Target注解修飾的注解就可以作用于泛型上,
*
* @since 1.8 (通過(guò)這個(gè)注釋可以知道,這個(gè)類(lèi)型是在jdk1.8才引入的)
*/
TYPE_PARAMETER,
/**
* TYPE_USE類(lèi)型是什么呢?
* 它是用于聲明注解可以作用于任何類(lèi)型的代碼元素的。
* 這怎么理解呢?
* 我們?cè)谏厦嬲f(shuō)過(guò)同一個(gè)注解是可以作用于多種類(lèi)型的代碼元素上的,如果我們要作用于多種代
* 碼元素,那么我們可能需要通過(guò)@Target指定多種元素類(lèi)型,現(xiàn)在在jdk1.8引入了TYPE_USE
* 這個(gè)類(lèi)型之后,我們的寫(xiě)法可以簡(jiǎn)單一些了:
* 如果想指定注解可作用于所有類(lèi)型的代碼元素,不用像之前那樣把每一種代碼元素對(duì)應(yīng)的類(lèi)型
* 都傳給@Target注解了,只需要對(duì)@Target指定TYPE_USE類(lèi)型即可使注解對(duì)所有類(lèi)型的代
* 碼元素生效。
*
* @since 1.8(通過(guò)這個(gè)注釋可以知道,這個(gè)類(lèi)型是在jdk1.8才引入的)
*/
TYPE_USE
}
我們查看一下@Target元注解的定義:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}
可以看到value()方法的返回值類(lèi)型是一個(gè)數(shù)組ElementType[],這說(shuō)明@Target元注解可以傳入多種代碼元素類(lèi)型,意味著它修飾的注解將可以作用于多種代碼元素。
注意:
1)如果開(kāi)發(fā)者在定義一個(gè)注解時(shí),沒(méi)有使用@Target元注解進(jìn)行修飾,那么默認(rèn)這個(gè)注解可作用于所有類(lèi)型的代碼元素(不包括泛型)
2)不使用@Target元注解修飾與使用@Target元注解修飾并指定傳入類(lèi)型為T(mén)YPE_USE這兩種情況的作用差不多,區(qū)別在于:
不使用@Target元注解修飾的話,可作用于除了泛型之外的所有類(lèi)型的代碼元素;
而使用@Target元注解修飾并指定傳入類(lèi)型為T(mén)YPE_USE的話,對(duì)所有類(lèi)型的代碼元素都生效,包括泛型。
有的小伙伴可能會(huì)有疑問(wèn),我看圖片的列表里還有@Native這個(gè)注解啊,它不是元注解嗎?為什么不介紹它呢?
其實(shí)啊,@Native這個(gè)注解還真不是元注解,我們可以點(diǎn)擊進(jìn)入@Native注解的源碼看一下:
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Native {
}
可以看到修飾@Native注解使用的元注解@Target(ElementType.FIELD)指示出@Native注解的作用對(duì)象類(lèi)型是FIELD,也就是屬性而不是注解ANNOTATION_TYPE,那么它就只能用來(lái)修飾屬性而不能用來(lái)修飾注解;
用來(lái)修飾注解的注解才叫元注解,所以@Native注解不是元注解。
哪些又是普通注解?
java中常見(jiàn)的普通注解有:
@Override
@Deprecated
@SuppressWarnings
@Override
首先我們看下@Override注解在jdk源碼中的定義:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
通過(guò)@Target元注解指定@Override注解的目標(biāo)代碼元素是METHOD,通過(guò)@Retention元注解指定@Override注解的保留階段是源碼階段,即只會(huì)保留在.java代碼中。
通過(guò)這個(gè)定義我們知道@Override注解是只能作用于方法元素的,它的作用是用于標(biāo)識(shí)某個(gè)方法是重寫(xiě)的父類(lèi)方法。
那么@Override注解其實(shí)就相當(dāng)于一個(gè)標(biāo)識(shí),編譯器在編譯階段通過(guò)這個(gè)標(biāo)識(shí)知道這個(gè)方法是重寫(xiě)自父類(lèi)的,那么就需要檢查其父類(lèi)中是否定義了這個(gè)方法,因?yàn)橹挥懈割?lèi)中定義了,子類(lèi)才有重寫(xiě)這一說(shuō)法,否則在編譯階段要拋出異常,讓開(kāi)發(fā)者盡早知道問(wèn)題并解決問(wèn)題。
@Deprecated
看下@Deprecated注解在jdk源碼中的定義:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
它的保留階段持續(xù)到運(yùn)行時(shí),可作用代碼元素包括:CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE。
它的作用是用于標(biāo)識(shí)某個(gè)代碼元素在當(dāng)前的版本中已經(jīng)過(guò)時(shí)了,不建議再使用。

如圖,過(guò)時(shí)的元素會(huì)在中部加一條線,提示這個(gè)元素過(guò)時(shí)了。
@SuppressWarnings
看下@SuppressWarnings注解在jdk源碼中的定義:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
@SuppressWarnings注解保留階段為源碼階段,@SuppressWarnings注解的可作用代碼元素類(lèi)型為:TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE。
@SuppressWarnings注解value()方法的返回值是String[]數(shù)組類(lèi)型,所以@SuppressWarnings注解可以傳入單個(gè)String類(lèi)型值也可以傳入一個(gè)String數(shù)組,如:


還可以這樣傳值:

@SuppressWarnings注解的作用是:告訴編譯器,當(dāng)代碼元素上使用了@SuppressWarnings注解時(shí),如果該代碼元素在編譯過(guò)程中發(fā)生了@SuppressWarnings注解指定的告警類(lèi)型的話,那么編譯器需要忽略該類(lèi)型的告警信息。
具體忽略哪種類(lèi)型的告警信息,通過(guò)@SuppressWarnings傳入的告警類(lèi)型決定。
@SuppressWarnings注解可傳入的常見(jiàn)告警類(lèi)型整理如下:
unchecked:抑制未檢查的轉(zhuǎn)化,例如集合沒(méi)有指定類(lèi)型的警告
unused:抑制未使用的變量的警告
resource:抑制與使用Closeable類(lèi)型資源相關(guān)的警告
path:抑制在類(lèi)路徑,原文件路徑中有不存在的路徑的警告
deprecation:抑制使用了某些不贊成使用的類(lèi)和方法的警告
fallthrough:抑制switch語(yǔ)句執(zhí)行到底沒(méi)有break關(guān)鍵字的警告
serial:抑制某類(lèi)實(shí)現(xiàn)Serializable,但是沒(méi)有定義serialVersionUID,這個(gè)需要但是不必須的字段的警告
rawtypes:抑制沒(méi)有傳遞帶有泛型的參數(shù)的警告
all:抑制全部類(lèi)型的警告
————————————————————————————————————————————————
這里講解的這些注解都是作用于非注解的代碼元素的,所以它們都是普通注解,并且由于它們是java軟件包中已經(jīng)事先定義好的了,所以它們也是java的內(nèi)置注解。
2、java如何自定義注解?
在前面的那一小節(jié)中,我其實(shí)已經(jīng)多次使用了自定義注解了,但是有些規(guī)范以及注意事項(xiàng)我還想單獨(dú)通過(guò)一個(gè)小節(jié)進(jìn)行講解。
首先我們應(yīng)該認(rèn)識(shí)到的一點(diǎn)是:java內(nèi)置注解與自定義注解本質(zhì)上沒(méi)有區(qū)別,如果硬要找一個(gè)不同點(diǎn)的話,那么就是內(nèi)置注解是jdk發(fā)包時(shí)就已經(jīng)定義好的了,已經(jīng)存在于java的軟件包當(dāng)中,而自定義注解則是由于開(kāi)發(fā)人員根據(jù)不同的需求自己定義的。
如何自定義一個(gè)注解:
1)創(chuàng)建一個(gè)新的.java文件,并指定類(lèi)型為Annotation類(lèi)型。

2)定義得到的注解如下:
使用元注解@Retention指定注解的保留階段,或者說(shuō)生命周期,如這里指定的保留階段是運(yùn)行時(shí)階段。
使用元注解@Target指定注解可作用的代碼元素類(lèi)型,如這里指定的代碼元素類(lèi)型是ElementType.TYPE,表示可作用于類(lèi)、接口或者枚舉聲明。
因?yàn)橐话闱闆r下,我們自定義注解都是針對(duì)特定的需求來(lái)設(shè)計(jì)的,所以一般都會(huì)通過(guò)相關(guān)的元注解修飾進(jìn)而限定自定義注解的使用。
如果不使用@Retention和@Target元注解的話,那么自定義注解的生命周期默認(rèn)是字節(jié)碼階段,而作用范圍默認(rèn)是所有類(lèi)型的代碼元素(不包括泛型)。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Person {
String value() default "";
}
根據(jù)上面注解的定義,我們可以看到:
1)注解的定義與接口的定義很相近,但是注解需要在interface前面添加@符號(hào),也就是注解定義的關(guān)鍵字是@interface
2)注解中聲明的方法可以通過(guò)default關(guān)鍵字指定默認(rèn)的返回值,但是指定默認(rèn)返回值并不是必要的
如果定義注解時(shí)使用default關(guān)鍵字指定了默認(rèn)值:
如果在使用注解時(shí)不傳入新值,則使用默認(rèn)值,如果傳入了新值,則使用傳入的值覆蓋默認(rèn)值;
如果定義注解時(shí)沒(méi)有使用default關(guān)鍵字指定默認(rèn)值:
那么在使用注解時(shí)必須要傳入數(shù)值,否則報(bào)錯(cuò)。
注解中方法的返回值類(lèi)型有哪些?
注解中方法支持的返回值類(lèi)型如下:
1)java的所有基本數(shù)據(jù)類(lèi)型(int、float、boolean、byte、double、char、long、short)
2)字符串類(lèi)型 String
3)字節(jié)碼類(lèi)型 Class
4)枚舉類(lèi)型 enum
5)注解類(lèi)型 Annotation
6)數(shù)組類(lèi)型,其中數(shù)組元素的類(lèi)型只能是上述類(lèi)型之一
也就是說(shuō),注解方法的返回值類(lèi)型不能是對(duì)象引用類(lèi)型,例如:
我定義一個(gè)類(lèi)Person
public class Person {
}
再定義一個(gè)注解@Role

在Role注解中使用Person類(lèi)型,結(jié)果報(bào)錯(cuò)了,注解的返回值類(lèi)型中沒(méi)有Person這種類(lèi)型
自定義注解本身能被繼承?
自定義注解本身是不能被繼承的,例如,我定義一個(gè)注解@A,再定義一個(gè)注解@B,并使注解A繼承注解B,如下圖:

報(bào)錯(cuò)了,自定義注解無(wú)法被繼承。
但是所有的注解最終都需要繼承java.lang.annotation.Annotation接口

對(duì)注解@Role的字節(jié)碼文件反編譯,可以得到上圖的信息,從圖中可以看到,注解Role繼承了接口java.lang.annotation.Annotation。
注解中方法的聲明需要注意什么嗎?
我們?cè)谑褂米⒔獠魅霐?shù)值時(shí),一般是通過(guò)key=value的格式進(jìn)行傳值的,其中key對(duì)應(yīng)的是方法名,value對(duì)應(yīng)的是傳給該方法的值,如@Role注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Role {
String role() default "";
}
在類(lèi)Person上使用@Role注解:
//Role注解的方法名為role,所以傳數(shù)值時(shí)key為注解的目標(biāo)方法名role,value為需要傳給注解存儲(chǔ)的值,即role="father"
@Role(role = "father")
public class Person {
}
如果注解中聲明了一個(gè)或多個(gè)方法,并且至少有一個(gè)方法名為value,那么在使用注解傳值時(shí)可以省略key,直接傳入數(shù)值;
即不管一個(gè)注解中聲明了多少個(gè)方法,只要其中含有名為value的方法,那么傳數(shù)值時(shí)都可以省略key,自然地,省略key情況下,傳入的數(shù)值都是傳給value方法的,想要給其他方法傳值,那么就需要顯式地通過(guò)key=value方式指定了;
當(dāng)然,對(duì)value方法傳值可以省略key,自然也可以指定key了,如果想要指定key的話,根據(jù)key為方法名的規(guī)則,指定的key為value:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Role {
//聲明了value()方法
String value() default "";
//聲明了test()方法
String test() default "";
}
在類(lèi)Person上使用Role注解:
省略key的情況,直接傳入數(shù)值
@Role("father")
public class Person {
public static void main(String[] args) {
//調(diào)用Role注解的value()方法獲取值,預(yù)期是可以獲取到father的
System.out.println(Person.class.getAnnotation(Role.class).value());
}
}
運(yùn)行結(jié)果與預(yù)期一致,獲取到了father,證明省略key的情況下,數(shù)值是傳給value()方法的;
調(diào)用Role注解的test()方法的話,無(wú)法獲取到father:

對(duì)value方法顯式指定key為value
@Role(value="father")
public class Person {
public static void main(String[] args) {
System.out.println(Person.class.getAnnotation(Role.class).test());
}
}
調(diào)用Role注解的value方法也可以獲取到father:

3、注解都可以用在哪里?
第一小節(jié)中講解@Target元注解的時(shí)候已經(jīng)講到了注解可以作用的代碼元素有哪些,這里再貼一邊代碼:
public enum ElementType {
/**
* 這個(gè)類(lèi)型表示的是類(lèi)、接口或者枚舉,
* 意思是,如果@Target注解指定的值是TYPE,那么被@Target注解修飾的注解就可以作用于類(lèi)、接口或者枚舉聲明上
*/
TYPE,
/** 這個(gè)類(lèi)型表示的是屬性,
* 意思是,如果@Target注解指定的值是FIELD,那么被@Target注解修飾的注解就可以作用于屬性上
*/
FIELD,
/** 這個(gè)類(lèi)型表示的是方法,
* 意思是,如果@Target注解指定的值是METHOD,那么被@Target注解修飾的注解就可以作用于方法上
*/
METHOD,
/** 這個(gè)類(lèi)型表示的是方法參數(shù),
* 意思是,如果@Target注解指定的值是PARAMETER,那么被@Target注解修飾的注解就可以作用于方法參數(shù)上
*/
PARAMETER,
/** 這個(gè)類(lèi)型表示的是構(gòu)造方法,
* 意思是,如果@Target注解指定的值是CONSTRUCTOR,那么被@Target注解修飾的注解就可以作用于構(gòu)造方法上
*/
CONSTRUCTOR,
/** 這個(gè)類(lèi)型表示的是局部變量,
* 意思是,如果@Target注解指定的值是LOCAL_VARIABLE,那么被@Target注解修飾的注解就可以作用于LOCAL_VARIABLE上,
* 哪些局部變量呢?比如在方法體內(nèi)部定義的變量
*/
LOCAL_VARIABLE,
/** 這個(gè)類(lèi)型表示的是注解,
* 意思是,如果@Target注解指定的值是ANNOTATION_TYPE,那么被@Target注解修飾的注解就可以作用于注解上,
* 注意請(qǐng)仔細(xì)看這里,這個(gè)說(shuō)明如果通過(guò)@Target指定的類(lèi)型是ANNOTATION_TYPE的話,那么當(dāng)前定義的這個(gè)注解就是個(gè)元注解,
* 它到時(shí)候是要作用于普通注解上的
*/
ANNOTATION_TYPE,
/** 這個(gè)類(lèi)型表示的是java包,
* 意思是,如果@Target注解指定的值是PACKAGE,那么被@Target注解修飾的注解就可以作用于java包上
*/
PACKAGE,
/**
* TYPE_PARAMETER的意思是類(lèi)型參數(shù),類(lèi)型參數(shù)是啥呢?它就是泛型。
* 泛型把類(lèi)型當(dāng)作一種參數(shù)來(lái)看代。
* 如果@Target注解指定的值是TYPE_PARAMETER,那么被@Target注解修飾的注解就可以作用于泛型上,
*
* @since 1.8 (通過(guò)這個(gè)注釋可以知道,這個(gè)類(lèi)型是在jdk1.8才引入的)
*/
TYPE_PARAMETER,
/**
* TYPE_USE類(lèi)型是什么呢?
* 它是用于聲明注解可以作用于任何類(lèi)型的代碼元素的。
* 這怎么理解呢?
* 我們?cè)谏厦嬲f(shuō)過(guò)同一個(gè)注解是可以作用于多種類(lèi)型的代碼元素上的,如果我們要作用于多種代
* 碼元素,那么我們可能需要通過(guò)@Target指定多種元素類(lèi)型,現(xiàn)在在jdk1.8引入了TYPE_USE
* 這個(gè)類(lèi)型之后,我們的寫(xiě)法可以簡(jiǎn)單一些了:
* 如果想指定注解可作用于所有類(lèi)型的代碼元素,不用像之前那樣把每一種代碼元素對(duì)應(yīng)的類(lèi)型
* 都傳給@Target注解了,只需要對(duì)@Target指定TYPE_USE類(lèi)型即可使注解對(duì)所有類(lèi)型的代
* 碼元素生效。
*
* @since 1.8(通過(guò)這個(gè)注釋可以知道,這個(gè)類(lèi)型是在jdk1.8才引入的)
*/
TYPE_USE
}
需要注意的是,TYPE_USE并不是代碼元素類(lèi)別,而是所有類(lèi)型的代碼元素的一個(gè)統(tǒng)稱(chēng)。
即要指代對(duì)所有類(lèi)型的代碼元素有效時(shí),可以直接使用TYPE_USE。
除了TYPE_USE,其余的枚舉值都表示代碼元素。
參考文章 :
https://blog.csdn.net/javazejian/article/details/71860633?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.not_use_machine_learn_pai&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.not_use_machine_learn_pai
https://cloud.tencent.com/developer/article/1353329