關(guān)于注解首先引入官方文檔的一句話:Java 注解用于為 Java 代碼提供元數(shù)據(jù)。作為元數(shù)據(jù),注解不直接影響你的代碼執(zhí)行,但也有一些類(lèi)型的注解實(shí)際上可以用于這一目的。Java 注解是從 Java5 開(kāi)始添加到 Java 的。看完這句話也許你還是一臉懵逼,接下我將從注解的定義、元注解、注解屬性、自定義注解、注解解析JDK 提供的注解這幾個(gè)方面再次了解注解(Annotation)
我自己是一個(gè)從事了6年的Java全棧工程師,最近整理了一套適合2019年學(xué)習(xí)的Java\大數(shù)據(jù)資料,從基礎(chǔ)的Java、大數(shù)據(jù)面向?qū)ο蟮竭M(jìn)階的框架知識(shí)都有整理哦,可以來(lái)我的主頁(yè)免費(fèi)領(lǐng)取哦
注解的定義
日常開(kāi)發(fā)中新建Java類(lèi),我們使用class、interface比較多,而注解和它們一樣,也是一種類(lèi)的類(lèi)型,他是用的修飾符為 @interface
注解類(lèi)的寫(xiě)法最近整理了一套適合2019年學(xué)習(xí)的Java\大數(shù)據(jù)資料,從基礎(chǔ)的Java、大數(shù)據(jù)面向?qū)ο蟮竭M(jìn)階的框架知識(shí)都有整理哦,可以來(lái)我的主頁(yè)免費(fèi)領(lǐng)取哦。
我們新建一個(gè)注解MyTestAnnotation
public@interfaceMyTestAnnotation{}復(fù)制代碼
接著我們就可以在類(lèi)或者方法上作用我們剛剛新建的注解
@MyTestAnnotationpublicclasstest{@MyTestAnnotationpublicstaticvoidmain(String[] args){? }}復(fù)制代碼
以上我們只是了解了注解的寫(xiě)法,但是我們定義的注解中還沒(méi)寫(xiě)任何代碼,現(xiàn)在這個(gè)注解毫無(wú)意義,要如何使注解工作呢?接下來(lái)我們接著了解元注解。
元注解
元注解顧名思義我們可以理解為注解的注解,它是作用在注解中,方便我們使用注解實(shí)現(xiàn)想要的功能。元注解分別有@Retention、 @Target、 @Document、 @Inherited和@Repeatable(JDK1.8加入)五種。
@Retention
Retention英文意思有保留、保持的意思,它表示注解存在階段是保留在源碼(編譯期),字節(jié)碼(類(lèi)加載)或者運(yùn)行期(JVM中運(yùn)行)。在@Retention注解中使用枚舉RetentionPolicy來(lái)表示注解保留時(shí)期
@Retention(RetentionPolicy.SOURCE),注解僅存在于源碼中,在class字節(jié)碼文件中不包含
@Retention(RetentionPolicy.CLASS), 默認(rèn)的保留策略,注解會(huì)在class字節(jié)碼文件中存在,但運(yùn)行時(shí)無(wú)法獲得
@Retention(RetentionPolicy.RUNTIME), 注解會(huì)在class字節(jié)碼文件中存在,在運(yùn)行時(shí)可以通過(guò)反射獲取到
如果我們是自定義注解,則通過(guò)前面分析,我們自定義注解如果只存著源碼中或者字節(jié)碼文件中就無(wú)法發(fā)揮作用,而在運(yùn)行期間能獲取到注解才能實(shí)現(xiàn)我們目的,所以自定義注解中肯定是使用?@Retention(RetentionPolicy.RUNTIME)
@Retention(RetentionPolicy.RUNTIME)public@interfaceMyTestAnnotation {}復(fù)制代碼
@Target
Target的英文意思是目標(biāo),這也很容易理解,使用@Target元注解表示我們的注解作用的范圍就比較具體了,可以是類(lèi),方法,方法參數(shù)變量等,同樣也是通過(guò)枚舉類(lèi)ElementType表達(dá)作用類(lèi)型
@Target(ElementType.TYPE) 作用接口、類(lèi)、枚舉、注解
@Target(ElementType.FIELD) 作用屬性字段、枚舉的常量
@Target(ElementType.METHOD) 作用方法
@Target(ElementType.PARAMETER) 作用方法參數(shù)
@Target(ElementType.CONSTRUCTOR) 作用構(gòu)造函數(shù)
@Target(ElementType.LOCAL_VARIABLE)作用局部變量
@Target(ElementType.ANNOTATION_TYPE)作用于注解(@Retention注解中就使用該屬性)
@Target(ElementType.PACKAGE) 作用于包
@Target(ElementType.TYPE_PARAMETER) 作用于類(lèi)型泛型,即泛型方法、泛型類(lèi)、泛型接口 (jdk1.8加入)
@Target(ElementType.TYPE_USE) 類(lèi)型使用.可以用于標(biāo)注任意類(lèi)型除了 class (jdk1.8加入)
一般比較常用的是ElementType.TYPE類(lèi)型
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public@interfaceMyTestAnnotation {}復(fù)制代碼
@Documented
Document的英文意思是文檔。它的作用是能夠?qū)⒆⒔庵械脑匕?Javadoc 中去。
@Inherited
Inherited的英文意思是繼承,但是這個(gè)繼承和我們平時(shí)理解的繼承大同小異,一個(gè)被@Inherited注解了的注解修飾了一個(gè)父類(lèi),如果他的子類(lèi)沒(méi)有被其他注解修飾,則它的子類(lèi)也繼承了父類(lèi)的注解。
下面我們來(lái)看個(gè)@Inherited注解例子
/**自定義注解*/@Documented@Inherited@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public@interfaceMyTestAnnotation{}/**父類(lèi)標(biāo)注自定義注解*/@MyTestAnnotationpublicclassFather{}/**子類(lèi)*/publicclassSonextendsFather{}/**測(cè)試子類(lèi)獲取父類(lèi)自定義注解*/publicclasstest{? public static void main(String[] args){//獲取Son的class對(duì)象Class sonClass =Son.class;// 獲取Son類(lèi)上的注解MyTestAnnotation可以執(zhí)行成功MyTestAnnotationannotation = sonClass.getAnnotation(MyTestAnnotation.class);? }}復(fù)制代碼
@Repeatable
Repeatable的英文意思是可重復(fù)的。顧名思義說(shuō)明被這個(gè)元注解修飾的注解可以同時(shí)作用一個(gè)對(duì)象多次,但是每次作用注解又可以代表不同的含義。
下面我們看一個(gè)人玩游戲的例子
/**一個(gè)人喜歡玩游戲,他喜歡玩英雄聯(lián)盟,絕地求生,極品飛車(chē),塵埃4等,則我們需要定義一個(gè)人的注解,他屬性代表喜歡玩游戲集合,一個(gè)游戲注解,游戲?qū)傩源碛螒蛎Q(chēng)*//**玩家注解*/@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public@interfacePeople {? ? Game[] value() ;}/**游戲注解*/@Repeatable(People.class)@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public@interfaceGame {? ? String value()default"";}/**玩游戲類(lèi)*/@Game(value ="LOL")@Game(value ="PUBG")@Game(value ="NFS")@Game(value ="Dirt4")publicclassPlayGame{}復(fù)制代碼
通過(guò)上面的例子,你可能會(huì)有一個(gè)疑問(wèn),游戲注解中括號(hào)的變量是啥,其實(shí)這和游戲注解中定義的屬性對(duì)應(yīng)。接下來(lái)我們繼續(xù)學(xué)習(xí)注解的屬性。
注解的屬性
通過(guò)上一小節(jié)@Repeatable注解的例子,我們說(shuō)到注解的屬性。注解的屬性其實(shí)和類(lèi)中定義的變量有異曲同工之處,只是注解中的變量都是成員變量(屬性),并且注解中是沒(méi)有方法的,只有成員變量,變量名就是使用注解括號(hào)中對(duì)應(yīng)的參數(shù)名,變量返回值注解括號(hào)中對(duì)應(yīng)參數(shù)類(lèi)型。相信這會(huì)你應(yīng)該會(huì)對(duì)上面的例子有一個(gè)更深的認(rèn)識(shí)。而@Repeatable注解中的變量則類(lèi)型則是對(duì)應(yīng)Annotation(接口)的泛型Class。
/**注解Repeatable源碼*/@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)public@interfaceRepeatable {/**? ? * Indicates thecontaining annotation typeforthe? ? * repeatable annotation type.? ? * @return thecontaining annotation type? ? */Class value();}復(fù)制代碼
注解的本質(zhì)
注解的本質(zhì)就是一個(gè)Annotation接口
/**Annotation接口源碼*/publicinterfaceAnnotation{? ? ? boolean equals(Objectobj);inthashCode();? ? ? ? Class annotationType();}復(fù)制代碼
通過(guò)以上源碼,我們知道注解本身就是Annotation接口的子接口,?也就是說(shuō)注解中其實(shí)是可以有屬性和方法,但是接口中的屬性都是static final的,對(duì)于注解來(lái)說(shuō)沒(méi)什么意義,而我們定義接口的方法就相當(dāng)于注解的屬性,也就對(duì)應(yīng)了前面說(shuō)的為什么注解只有屬性成員變量,其實(shí)他就是接口的方法,這就是為什么成員變量會(huì)有括號(hào)?,不同于接口我們可以在注解的括號(hào)中給成員變量賦值。
注解屬性類(lèi)型
注解屬性類(lèi)型可以有以下列出的類(lèi)型
1.基本數(shù)據(jù)類(lèi)型
2.String
3.枚舉類(lèi)型
4.注解類(lèi)型
5.Class類(lèi)型
6.以上類(lèi)型的一維數(shù)組類(lèi)型
注解成員變量賦值
如果注解又多個(gè)屬性,則可以在注解括號(hào)中用“,”號(hào)隔開(kāi)分別給對(duì)應(yīng)的屬性賦值,如下例子,注解在父類(lèi)中賦值屬性
@Documented@Inherited@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public@interfaceMyTestAnnotation {? ? String name()default"mao";? ? int age()default18;}@MyTestAnnotation(name ="father",age = 50)publicclassFather{}復(fù)制代碼
獲取注解屬性
前面我們說(shuō)了很多注解如何定義,放在哪,現(xiàn)在我們可以開(kāi)始學(xué)習(xí)注解屬性的提取了,這才是使用注解的關(guān)鍵,獲取屬性的值才是使用注解的目的。
如果獲取注解屬性,當(dāng)然是反射啦,主要有三個(gè)基本的方法
/**是否存在對(duì)應(yīng) Annotation 對(duì)象*/publicboolean isAnnotationPresent(ClassannotationClass){returnGenericDeclaration.super.isAnnotationPresent(annotationClass);? ? }/**獲取 Annotation 對(duì)象*/public A getAnnotation(ClassannotationClass){? ? ? ? Objects.requireNonNull(annotationClass);return(A) annotationData().annotations.get(annotationClass);? ? }/**獲取所有 Annotation 對(duì)象數(shù)組*/publicAnnotation[] getAnnotations() {returnAnnotationParser.toArray(annotationData().annotations);? ? }? ? 復(fù)制代碼
下面結(jié)合前面的例子,我們來(lái)獲取一下注解屬性,在獲取之前我們自定義的注解必須使用元注解@Retention(RetentionPolicy.RUNTIME)
publicclasstest {publicstaticvoidmain(String[] args)throwsNoSuchMethodException {/**
? ? ? ? * 獲取類(lèi)注解屬性
? ? ? ? */Class fatherClass = Father.class;booleanannotationPresent = fatherClass.isAnnotationPresent(MyTestAnnotation.class);if(annotationPresent){? ? ? ? ? ? MyTestAnnotation annotation = fatherClass.getAnnotation(MyTestAnnotation.class);? ? ? ? ? ? System.out.println(annotation.name());? ? ? ? ? ? System.out.println(annotation.age());? ? ? ? }/**
? ? ? ? * 獲取方法注解屬性
? ? ? ? */try{? ? ? ? ? ? Field age = fatherClass.getDeclaredField("age");booleanannotationPresent1 = age.isAnnotationPresent(Age.class);if(annotationPresent1){? ? ? ? ? ? ? ? Age annotation = age.getAnnotation(Age.class);? ? ? ? ? ? ? ? System.out.println(annotation.value());? ? ? ? ? ? }? ? ? ? ? ? Method play = PlayGame.class.getDeclaredMethod("play");if(play!=null){? ? ? ? ? ? ? ? People annotation2 = play.getAnnotation(People.class);? ? ? ? ? ? ? ? Game[] value = annotation2.value();for(Game game : value) {? ? ? ? ? ? ? ? ? ? System.out.println(game.value());? ? ? ? ? ? ? ? }? ? ? ? ? ? }? ? ? ? }catch(NoSuchFieldException e) {? ? ? ? ? ? e.printStackTrace();? ? ? ? }? ? }}復(fù)制代碼
運(yùn)行結(jié)果:
JDK 提供的注解
注解作用注意事項(xiàng)
@Override它是用來(lái)描述當(dāng)前方法是一個(gè)重寫(xiě)的方法,在編譯階段對(duì)方法進(jìn)行檢查jdk1.5中它只能描述繼承中的重寫(xiě),jdk1.6中它可以描述接口實(shí)現(xiàn)的重寫(xiě),也能描述類(lèi)的繼承的重寫(xiě)
@Deprecated它是用于描述當(dāng)前方法是一個(gè)過(guò)時(shí)的方法無(wú)
@SuppressWarnings對(duì)程序中的警告去除。無(wú)
注解作用與應(yīng)用
現(xiàn)在我們?cè)俅位仡^看看開(kāi)頭官方文檔的那句描述
Java 注解用于為 Java 代碼提供元數(shù)據(jù)。作為元數(shù)據(jù),注解不直接影響你的代碼執(zhí)行,但也有一些類(lèi)型的注解實(shí)際上可以用于這一目的。
經(jīng)過(guò)我們前面的了解,注解其實(shí)是個(gè)很方便的東西,它存活的時(shí)間,作用的區(qū)域都可以由你方便設(shè)置,只是你用注解來(lái)干嘛的問(wèn)題
使用注解進(jìn)行參數(shù)配置
下面我們看一個(gè)銀行轉(zhuǎn)賬的例子,假設(shè)銀行有個(gè)轉(zhuǎn)賬業(yè)務(wù),轉(zhuǎn)賬的限額可能會(huì)根據(jù)匯率的變化而變化,我們可以利用注解靈活配置轉(zhuǎn)賬的限額,而不用每次都去修改我們的業(yè)務(wù)代碼。
/**定義限額注解*/@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public@interfaceBankTransferMoney {doublemaxMoney()default10000;}/**轉(zhuǎn)賬處理業(yè)務(wù)類(lèi)*/publicclassBankService{/**? ? * @param money 轉(zhuǎn)賬金額? ? */@BankTransferMoney(maxMoney =15000)? ? publicstaticvoidTransferMoney(doublemoney){? ? ? ? System.out.println(processAnnotationMoney(money));? ? }? ? privatestaticStringprocessAnnotationMoney(doublemoney) {try{? ? ? ? ? ? Method transferMoney = BankService.class.getDeclaredMethod("TransferMoney",double.class);? ? ? ? ? ? boolean annotationPresent = transferMoney.isAnnotationPresent(BankTransferMoney.class);if(annotationPresent){? ? ? ? ? ? ? ? BankTransferMoney annotation = transferMoney.getAnnotation(BankTransferMoney.class);doublel = annotation.maxMoney();if(money>l){return"轉(zhuǎn)賬金額大于限額,轉(zhuǎn)賬失敗";? ? ? ? ? ? ? ? }else{return"轉(zhuǎn)賬金額為:"+money+",轉(zhuǎn)賬成功";? ? ? ? ? ? ? ? }? ? ? ? ? ? }? ? ? ? }catch( NoSuchMethodException e) {? ? ? ? ? ? e.printStackTrace();? ? ? ? }return"轉(zhuǎn)賬處理失敗";? ? }? ? publicstaticvoidmain(String[] args){? ? ? ? TransferMoney(10000);? ? }}復(fù)制代碼
運(yùn)行結(jié)果:
通過(guò)上面的例子,只要匯率變化,我們就改變注解的配置值就可以直接改變當(dāng)前最大限額。
第三方框架的應(yīng)用
作為一個(gè)Android 開(kāi)發(fā)者,平常我們所使用的第三方框架ButterKnife,Retrofit2,Dagger2等都有注解的應(yīng)用,如果我們要了解這些框架的原理,則注解的基礎(chǔ)知識(shí)則是必不可少的。
注解的作用
提供信息給編譯器: 編譯器可以利用注解來(lái)檢測(cè)出錯(cuò)誤或者警告信息,打印出日志。
編譯階段時(shí)的處理: 軟件工具可以用來(lái)利用注解信息來(lái)自動(dòng)生成代碼、文檔或者做其它相應(yīng)的自動(dòng)處理。
運(yùn)行時(shí)處理: 某些注解可以在程序運(yùn)行的時(shí)候接受代碼的提取,自動(dòng)做相應(yīng)的操作。
正如官方文檔的那句話所說(shuō),注解能夠提供元數(shù)據(jù),轉(zhuǎn)賬例子中處理獲取注解值的過(guò)程是我們開(kāi)發(fā)者直接寫(xiě)的注解提取邏輯,?處理提取和處理 Annotation 的代碼統(tǒng)稱(chēng)為 APT(Annotation Processing Tool)?。上面轉(zhuǎn)賬例子中的processAnnotationMoney方法就可以理解為APT工具類(lèi)。
最近整理了一套適合2019年學(xué)習(xí)的Java\大數(shù)據(jù)資料,從基礎(chǔ)的Java、大數(shù)據(jù)面向?qū)ο蟮竭M(jìn)階的框架知識(shí)都有整理哦,可以來(lái)我的主頁(yè)免費(fèi)領(lǐng)取哦。