關(guān)于注解首先引入官方文檔的一句話:Java 注解用于為 Java 代碼提供元數(shù)據(jù)悔常。作為元數(shù)據(jù)球订,注解不直接影響你的代碼執(zhí)行筝家,但也有一些類型的注解實際上可以用于這一目的轧钓。Java 注解是從 Java5 開始添加到 Java 的序厉。看完這句話也許你還是一臉懵逼毕箍,接下我將從注解的定義弛房、元注解、注解屬性而柑、自定義注解文捶、注解解析JDK 提供的注解這幾個方面再次了解注解(Annotation)
我自己是一個從事了6年的Java全棧工程師荷逞,最近整理了一套適合2019年學(xué)習(xí)的Java\大數(shù)據(jù)資料,從基礎(chǔ)的Java粹排、大數(shù)據(jù)面向?qū)ο蟮竭M(jìn)階的框架知識都有整理哦种远,可以來我的主頁免費(fèi)領(lǐng)取哦
注解的定義
日常開發(fā)中新建Java類,我們使用class顽耳、interface比較多坠敷,而注解和它們一樣,也是一種類的類型射富,他是用的修飾符為 @interface
注解類的寫法最近整理了一套適合2019年學(xué)習(xí)的Java\大數(shù)據(jù)資料膝迎,從基礎(chǔ)的Java、大數(shù)據(jù)面向?qū)ο蟮竭M(jìn)階的框架知識都有整理哦胰耗,可以來我的主頁免費(fèi)領(lǐng)取哦限次。
我們新建一個注解MyTestAnnotation
public@interfaceMyTestAnnotation{}復(fù)制代碼
接著我們就可以在類或者方法上作用我們剛剛新建的注解
@MyTestAnnotationpublicclasstest{@MyTestAnnotationpublicstaticvoidmain(String[] args){? }}復(fù)制代碼
以上我們只是了解了注解的寫法,但是我們定義的注解中還沒寫任何代碼宪郊,現(xiàn)在這個注解毫無意義掂恕,要如何使注解工作呢?接下來我們接著了解元注解弛槐。
元注解
元注解顧名思義我們可以理解為注解的注解懊亡,它是作用在注解中,方便我們使用注解實現(xiàn)想要的功能乎串。元注解分別有@Retention店枣、 @Target、 @Document叹誉、 @Inherited和@Repeatable(JDK1.8加入)五種鸯两。
@Retention
Retention英文意思有保留、保持的意思长豁,它表示注解存在階段是保留在源碼(編譯期)钧唐,字節(jié)碼(類加載)或者運(yùn)行期(JVM中運(yùn)行)。在@Retention注解中使用枚舉RetentionPolicy來表示注解保留時期
@Retention(RetentionPolicy.SOURCE)匠襟,注解僅存在于源碼中钝侠,在class字節(jié)碼文件中不包含
@Retention(RetentionPolicy.CLASS), 默認(rèn)的保留策略酸舍,注解會在class字節(jié)碼文件中存在帅韧,但運(yùn)行時無法獲得
@Retention(RetentionPolicy.RUNTIME), 注解會在class字節(jié)碼文件中存在啃勉,在運(yùn)行時可以通過反射獲取到
如果我們是自定義注解忽舟,則通過前面分析,我們自定義注解如果只存著源碼中或者字節(jié)碼文件中就無法發(fā)揮作用,而在運(yùn)行期間能獲取到注解才能實現(xiàn)我們目的叮阅,所以自定義注解中肯定是使用?@Retention(RetentionPolicy.RUNTIME)
@Retention(RetentionPolicy.RUNTIME)public@interfaceMyTestAnnotation {}復(fù)制代碼
@Target
Target的英文意思是目標(biāo)刁品,這也很容易理解,使用@Target元注解表示我們的注解作用的范圍就比較具體了帘饶,可以是類哑诊,方法,方法參數(shù)變量等及刻,同樣也是通過枚舉類ElementType表達(dá)作用類型
@Target(ElementType.TYPE) 作用接口镀裤、類、枚舉缴饭、注解
@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) 作用于類型泛型,即泛型方法颗搂、泛型類担猛、泛型接口 (jdk1.8加入)
@Target(ElementType.TYPE_USE) 類型使用.可以用于標(biāo)注任意類型除了 class (jdk1.8加入)
一般比較常用的是ElementType.TYPE類型
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public@interfaceMyTestAnnotation {}復(fù)制代碼
@Documented
Document的英文意思是文檔。它的作用是能夠?qū)⒆⒔庵械脑匕?Javadoc 中去丢氢。
@Inherited
Inherited的英文意思是繼承傅联,但是這個繼承和我們平時理解的繼承大同小異,一個被@Inherited注解了的注解修飾了一個父類疚察,如果他的子類沒有被其他注解修飾蒸走,則它的子類也繼承了父類的注解。
下面我們來看個@Inherited注解例子
/**自定義注解*/@Documented@Inherited@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public@interfaceMyTestAnnotation{}/**父類標(biāo)注自定義注解*/@MyTestAnnotationpublicclassFather{}/**子類*/publicclassSonextendsFather{}/**測試子類獲取父類自定義注解*/publicclasstest{? public static void main(String[] args){//獲取Son的class對象Class sonClass =Son.class;// 獲取Son類上的注解MyTestAnnotation可以執(zhí)行成功MyTestAnnotationannotation = sonClass.getAnnotation(MyTestAnnotation.class);? }}復(fù)制代碼
@Repeatable
Repeatable的英文意思是可重復(fù)的貌嫡。顧名思義說明被這個元注解修飾的注解可以同時作用一個對象多次比驻,但是每次作用注解又可以代表不同的含義。
下面我們看一個人玩游戲的例子
/**一個人喜歡玩游戲岛抄,他喜歡玩英雄聯(lián)盟别惦,絕地求生,極品飛車夫椭,塵埃4等掸掸,則我們需要定義一個人的注解,他屬性代表喜歡玩游戲集合蹭秋,一個游戲注解扰付,游戲?qū)傩源碛螒蛎Q*//**玩家注解*/@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"";}/**玩游戲類*/@Game(value ="LOL")@Game(value ="PUBG")@Game(value ="NFS")@Game(value ="Dirt4")publicclassPlayGame{}復(fù)制代碼
通過上面的例子,你可能會有一個疑問感凤,游戲注解中括號的變量是啥悯周,其實這和游戲注解中定義的屬性對應(yīng)粒督。接下來我們繼續(xù)學(xué)習(xí)注解的屬性陪竿。
注解的屬性
通過上一小節(jié)@Repeatable注解的例子,我們說到注解的屬性。注解的屬性其實和類中定義的變量有異曲同工之處族跛,只是注解中的變量都是成員變量(屬性)闰挡,并且注解中是沒有方法的,只有成員變量礁哄,變量名就是使用注解括號中對應(yīng)的參數(shù)名长酗,變量返回值注解括號中對應(yīng)參數(shù)類型。相信這會你應(yīng)該會對上面的例子有一個更深的認(rèn)識桐绒。而@Repeatable注解中的變量則類型則是對應(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ì)就是一個Annotation接口
/**Annotation接口源碼*/publicinterfaceAnnotation{? ? ? boolean equals(Objectobj);inthashCode();? ? ? ? Class annotationType();}復(fù)制代碼
通過以上源碼,我們知道注解本身就是Annotation接口的子接口茉继,?也就是說注解中其實是可以有屬性和方法咧叭,但是接口中的屬性都是static final的,對于注解來說沒什么意義烁竭,而我們定義接口的方法就相當(dāng)于注解的屬性菲茬,也就對應(yīng)了前面說的為什么注解只有屬性成員變量,其實他就是接口的方法派撕,這就是為什么成員變量會有括號?婉弹,不同于接口我們可以在注解的括號中給成員變量賦值。
注解屬性類型
注解屬性類型可以有以下列出的類型
1.基本數(shù)據(jù)類型
2.String
3.枚舉類型
4.注解類型
5.Class類型
6.以上類型的一維數(shù)組類型
注解成員變量賦值
如果注解又多個屬性终吼,則可以在注解括號中用“镀赌,”號隔開分別給對應(yīng)的屬性賦值,如下例子衔峰,注解在父類中賦值屬性
@Documented@Inherited@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public@interfaceMyTestAnnotation {? ? String name()default"mao";? ? int age()default18;}@MyTestAnnotation(name ="father",age = 50)publicclassFather{}復(fù)制代碼
獲取注解屬性
前面我們說了很多注解如何定義佩脊,放在哪,現(xiàn)在我們可以開始學(xué)習(xí)注解屬性的提取了垫卤,這才是使用注解的關(guān)鍵威彰,獲取屬性的值才是使用注解的目的。
如果獲取注解屬性穴肘,當(dāng)然是反射啦歇盼,主要有三個基本的方法
/**是否存在對應(yīng) Annotation 對象*/publicboolean isAnnotationPresent(ClassannotationClass){returnGenericDeclaration.super.isAnnotationPresent(annotationClass);? ? }/**獲取 Annotation 對象*/public A getAnnotation(ClassannotationClass){? ? ? ? Objects.requireNonNull(annotationClass);return(A) annotationData().annotations.get(annotationClass);? ? }/**獲取所有 Annotation 對象數(shù)組*/publicAnnotation[] getAnnotations() {returnAnnotationParser.toArray(annotationData().annotations);? ? }? ? 復(fù)制代碼
下面結(jié)合前面的例子,我們來獲取一下注解屬性评抚,在獲取之前我們自定義的注解必須使用元注解@Retention(RetentionPolicy.RUNTIME)
publicclasstest {publicstaticvoidmain(String[] args)throwsNoSuchMethodException {/**
? ? ? ? * 獲取類注解屬性
? ? ? ? */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 提供的注解
注解作用注意事項
@Override它是用來描述當(dāng)前方法是一個重寫的方法豹缀,在編譯階段對方法進(jìn)行檢查jdk1.5中它只能描述繼承中的重寫,jdk1.6中它可以描述接口實現(xiàn)的重寫,也能描述類的繼承的重寫
@Deprecated它是用于描述當(dāng)前方法是一個過時的方法無
@SuppressWarnings對程序中的警告去除慨代。無
注解作用與應(yīng)用
現(xiàn)在我們再次回頭看看開頭官方文檔的那句描述
Java 注解用于為 Java 代碼提供元數(shù)據(jù)邢笙。作為元數(shù)據(jù),注解不直接影響你的代碼執(zhí)行侍匙,但也有一些類型的注解實際上可以用于這一目的氮惯。
經(jīng)過我們前面的了解,注解其實是個很方便的東西,它存活的時間妇汗,作用的區(qū)域都可以由你方便設(shè)置帘不,只是你用注解來干嘛的問題
使用注解進(jìn)行參數(shù)配置
下面我們看一個銀行轉(zhuǎn)賬的例子,假設(shè)銀行有個轉(zhuǎn)賬業(yè)務(wù)杨箭,轉(zhuǎn)賬的限額可能會根據(jù)匯率的變化而變化寞焙,我們可以利用注解靈活配置轉(zhuǎn)賬的限額,而不用每次都去修改我們的業(yè)務(wù)代碼互婿。
/**定義限額注解*/@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public@interfaceBankTransferMoney {doublemaxMoney()default10000;}/**轉(zhuǎn)賬處理業(yè)務(wù)類*/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é)果:
通過上面的例子慈参,只要匯率變化模她,我們就改變注解的配置值就可以直接改變當(dāng)前最大限額。
第三方框架的應(yīng)用
作為一個Android 開發(fā)者懂牧,平常我們所使用的第三方框架ButterKnife侈净,Retrofit2,Dagger2等都有注解的應(yīng)用僧凤,如果我們要了解這些框架的原理畜侦,則注解的基礎(chǔ)知識則是必不可少的。
注解的作用
提供信息給編譯器: 編譯器可以利用注解來檢測出錯誤或者警告信息躯保,打印出日志旋膳。
編譯階段時的處理: 軟件工具可以用來利用注解信息來自動生成代碼、文檔或者做其它相應(yīng)的自動處理途事。
運(yùn)行時處理: 某些注解可以在程序運(yùn)行的時候接受代碼的提取验懊,自動做相應(yīng)的操作。
正如官方文檔的那句話所說尸变,注解能夠提供元數(shù)據(jù)义图,轉(zhuǎn)賬例子中處理獲取注解值的過程是我們開發(fā)者直接寫的注解提取邏輯,?處理提取和處理 Annotation 的代碼統(tǒng)稱為 APT(Annotation Processing Tool)?召烂。上面轉(zhuǎn)賬例子中的processAnnotationMoney方法就可以理解為APT工具類碱工。
最近整理了一套適合2019年學(xué)習(xí)的Java\大數(shù)據(jù)資料,從基礎(chǔ)的Java奏夫、大數(shù)據(jù)面向?qū)ο蟮竭M(jìn)階的框架知識都有整理哦怕篷,可以來我的主頁免費(fèi)領(lǐng)取哦。