Java 注解深入理解
內(nèi)容概要
-
Annotation的概念
-
Annotation的作用
-
Annotation的分類
-
系統(tǒng)內(nèi)置注解
-
元注解
-
自定義注解
-
解析注解信息
-
JDK8注解新特性
附:項(xiàng)目源碼地址
一、Annotation的概念
Annotation(注解)是插入代碼中的元數(shù)據(jù),在JDK5.0及以后版本引入。它可以在編譯期使用預(yù)編譯工具進(jìn)行處理, 也可以在運(yùn)行期使用 Java 反射機(jī)制進(jìn)行處理,用于創(chuàng)建文檔,跟蹤代碼中的依賴性,甚至執(zhí)行基本編譯時(shí)檢查。因?yàn)楸举|(zhì)上,Annotion是一種特殊的接口,程序可以通過反射來獲取指定程序元素的Annotion對(duì)象,然后通過Annotion對(duì)象來獲取注解里面的元數(shù)據(jù)。(元數(shù)據(jù)從metadata一詞譯來,就是“關(guān)于數(shù)據(jù)的數(shù)據(jù)”的意思)
二、Annotation的作用
Annotation的作用大致可分為三類:
-
編寫文檔:通過代碼里標(biāo)識(shí)的元數(shù)據(jù)生成文檔;
-
代碼分析:通過代碼里標(biāo)識(shí)的元數(shù)據(jù)對(duì)代碼進(jìn)行分析;
-
編譯檢查:通過代碼里標(biāo)識(shí)的元數(shù)據(jù)讓編譯器能實(shí)現(xiàn)基本的編譯檢查;
綜上所述可知,Annotation主要用于提升軟件的質(zhì)量和提高軟件的生產(chǎn)效率。
三、Annotation的分類
- 根據(jù)成員個(gè)數(shù)分類
1.標(biāo)記注解:沒有定義成員的Annotation類型,自身代表某類信息,如:@Override
2.單成員注解:只定義了一個(gè)成員,比如@SuppressWarnings 定義了一個(gè)成員String[] value,使用value={…}大括號(hào)來聲明數(shù)組值,一般也可以省略“value=”
3.多成員注解:定義了多個(gè)成員,使用時(shí)以name=value對(duì)分別提供數(shù)據(jù)
- 根據(jù)注解使用的功能和用途分類
1.系統(tǒng)內(nèi)置注解:系統(tǒng)自帶的注解類型,如@Override
2.元注解:注解的注解,負(fù)責(zé)注解其他注解,如@Target
3.自定義注解:用戶根據(jù)自己的需求自定義的注解類型
四、系統(tǒng)內(nèi)置注解
- JavaSE中內(nèi)置三個(gè)標(biāo)準(zhǔn)注解,定義在java.lang中
1.@Override:用于修飾此方法覆蓋了父類的方法;
2.@Deprecated:用于修飾已經(jīng)過時(shí)的方法;
3.@SuppressWarnnings:用于通知java編譯器禁止特定的編譯警告;
- @Override 限定重寫父類方法
@Override 是一個(gè)標(biāo)記注解類型,它被用作標(biāo)注方法。它說明了被標(biāo)注的方法重寫了父類的方法,起到了斷言的作用。如果我們使用了這種Annotation在一個(gè)沒有覆蓋父類方法的方法時(shí),java編譯器將以一個(gè)編譯錯(cuò)誤來警示。
下面的代碼是一個(gè)使用@Override修飾一個(gè)企圖重寫父類的displayName()方法,而又存在拼寫錯(cuò)誤成displayname(),這時(shí)編譯器就會(huì)提示錯(cuò)誤:
public class Fruit{public void displayName(){System.out.println("水果的名字是:*****");} }class Orange extends Fruit{@Overridepublic void displayName(){System.out.println("水果的名字是:桔子");} }class Peach extends Fruit{@Overridepublic void displayname(){System.out.println("水果的名字是:桃子");} }Orange 類編譯不會(huì)有任何問題,Peach 類在編譯的時(shí)候會(huì)提示相應(yīng)的錯(cuò)誤;@Override注解只能用于方法,不能用于其他程序元素。
- @Deprecated 用于標(biāo)記已過時(shí)
Deprecated也是一個(gè)標(biāo)記注解。當(dāng)一個(gè)類型或者類型成員使用@Deprecated修飾的話,編譯器將不鼓勵(lì)使用這個(gè)被標(biāo)注的程序元素。而且這種修飾具有一定的 “延續(xù)性”:如果我們?cè)诖a中通過繼承或者覆蓋的方式使用了這個(gè)過時(shí)的類型或者成員,雖然繼承或者覆蓋后的類型或者成員并不是被聲明為 @Deprecated,但編譯器仍然要報(bào)警。
注意,@Deprecated這個(gè)annotation類型和javadoc中的 @deprecated這個(gè)tag是有區(qū)別的:前者是java編譯器識(shí)別的,而后者是被javadoc工具所識(shí)別用來生成文檔。
下面一段程序中使用了@Deprecated注解標(biāo)示方法過期,同時(shí)在方法注釋中用@deprecated tag 標(biāo)示該方法已經(jīng)過時(shí),代碼如下:
public class AppleService {public void displayName(){System.out.println("水果的名字是:蘋果");}/*** @deprecated 該方法已經(jīng)過期,不推薦使用*/@Deprecatedpublic void showTaste(){System.out.println("水果的蘋果的口感是:脆甜");}public void showTaste(int typeId){if(typeId==1){System.out.println("水果的蘋果的口感是:酸澀");}else if(typeId==2){System.out.println("水果的蘋果的口感是:綿甜");}else{System.out.println("水果的蘋果的口感是:脆甜");}} }public class AppleConsumer {//@SuppressWarnings({"deprecation"})public static void main(String[] args) {AppleService appleService=new AppleService();appleService.showTaste();appleService.showTaste(2);} }AppleService類的showTaste() 方法被@Deprecated標(biāo)注為過時(shí)方法,在AppleConsumer類中使用的時(shí)候,編譯器會(huì)給出該方法已過期,不推薦使用的提示。
- @SuppressWarnnings 抑制編譯器警告
@SuppressWarnings 其注解目標(biāo)為類、字段、函數(shù)、函數(shù)入?yún)ⅰ?gòu)造函數(shù)和函數(shù)的局部變量。在java5.0,sun提供的javac編譯器為我們提供了-Xlint選項(xiàng)來使編譯器對(duì)合法的程序代碼提出警告,此種警告從某種程度上代表了程序錯(cuò)誤。例如當(dāng)我們使用一個(gè)generic collection類而又沒有提供它的類型時(shí),編譯器將提示出”unchecked warning”的警告。通常當(dāng)這種情況發(fā)生時(shí),我們就需要查找引起警告的代碼。如果它真的表示錯(cuò)誤,我們就需要糾正它。例如如果警告信息表明我們代碼中的switch語(yǔ)句沒有覆蓋所有可能的case,那么我們就應(yīng)增加一個(gè)默認(rèn)的case來避免這種警告。
有時(shí)我們無法避免這種警告,例如,我們使用必須和非generic的舊代碼交互的generic collection類時(shí),我們不能避免這個(gè)unchecked warning。此時(shí)@SuppressWarning就要派上用場(chǎng)了,在調(diào)用的方法前增加@SuppressWarnings修飾,告訴編譯器停止對(duì)此方法的警告。
SuppressWarning不是一個(gè)標(biāo)記注解。它有一個(gè)類型為String[]的成員,這個(gè)成員的值為被禁止的警告名。使用示例如下:
public class SuppressWarningTest {@SuppressWarnings("unchecked")public void addItems2(String item){@SuppressWarnings("unused")List list = new ArrayList();List items = new ArrayList();items.add(item);}@SuppressWarnings({"unchecked","unused"})public void addItems1(String item){List list = new ArrayList();list.add(item);}@SuppressWarnings("all")public void addItems(String item){List list = new ArrayList();list.add(item);} }@SuppressWarnings注解的常見參數(shù)值:
1.deprecation:使用了不贊成使用的類或方法時(shí)的警告;
2.unchecked:執(zhí)行了未檢查的轉(zhuǎn)換時(shí)的警告,例如當(dāng)使用集合時(shí)沒有用泛型 (Generics) 來指定集合保存的類型;
3.fallthrough:當(dāng) Switch 程序塊直接通往下一種情況而沒有 Break 時(shí)的警告;
4.path:在類路徑、源文件路徑等中有不存在的路徑時(shí)的警告;
5.serial:當(dāng)在可序列化的類上缺少 serialVersionUID 定義時(shí)的警告;
6.finally:任何 finally 子句不能正常完成時(shí)的警告;
7.unused:代碼中的變量或方法沒有被使用產(chǎn)生的警告;
8.rawtypes:使用泛型時(shí)沒有指定類型的警告;
9.all:關(guān)于以上所有情況的警告。
10.更多關(guān)鍵字
五、元注解
元注解的作用就是負(fù)責(zé)注解其他注解。Java5.0定義了4個(gè)標(biāo)準(zhǔn)的meta-annotation類型,它們被用來提供對(duì)其它 annotation類型作說明。Java5.0定義的元注解:
1.@Target
2.@Retention
3.@Documented
4.@Inherited
- @Target
作用:描述該注解修飾的范圍,可被用于 packages、types(類、接口、枚舉、Annotation類型)、類型成員(方法、構(gòu)造方法、成員變量、枚舉值)、方法參數(shù)和本地變量(如循環(huán)變量、catch參數(shù))。
取值(ElementType):
1.CONSTRUCTOR:用于描述構(gòu)造器
2.FIELD:用于描述域
3.LOCAL_VARIABLE:用于描述局部變量
4.METHOD:用于描述方法
5.PACKAGE:用于描述包
6.PARAMETER:用于描述參數(shù)
7.TYPE:用于描述類、接口(包括注解類型) 或enum聲明
- @Retention
作用:描述該注解的生命周期,表示在什么編譯級(jí)別上保存該注解的信息。Annotation被保留的時(shí)間有長(zhǎng)短:某些Annotation僅出現(xiàn)在源代碼中,而被編譯器丟棄;而另一些卻被編譯在class文件中;編譯在class文件中的Annotation可能會(huì)被虛擬機(jī)忽略,而另一些在class被裝載時(shí)將被讀取(請(qǐng)注意并不影響class的執(zhí)行,因?yàn)锳nnotation與class在使用上是被分離的)。
取值(RetentionPoicy):
1.SOURCE:在源文件中有效(即源文件保留)
2.CLASS:在class文件中有效(即class保留) 3.RUNTIME:在運(yùn)行時(shí)有效(即運(yùn)行時(shí)保留)
- @Documented
@Documented Annotation的作用是在生成javadoc文檔的時(shí)候?qū)⒃揂nnotation也寫入到文檔中。
- @Inherited
作用:@Inherited 元注解是一個(gè)標(biāo)記注解,@Inherited闡述了某個(gè)被標(biāo)注的類型是被繼承的。如果一個(gè)使用了@Inherited修飾的annotation類型被用于一個(gè)class,則這個(gè)annotation將被用于該class的子類。
六、自定義注解
使用@interface自定義注解,自動(dòng)繼承了java.lang.annotation.Annotation接口,由編譯程序自動(dòng)完成其他細(xì)節(jié)。在定義注解時(shí),不能繼承其他的注解或接口。@interface用來聲明一個(gè)注解,其中的每一個(gè)方法實(shí)際上是聲明了一個(gè)配置參數(shù)。方法的名稱就是參數(shù)的名稱,返回值類型就是參數(shù)的類型(返回值類型只能是基本類型、Class、String、enum)。可以通過default來聲明參數(shù)的默認(rèn)值。
-
定義注解格式:?public @interface 注解名 {定義體}
-
注解參數(shù)的可支持?jǐn)?shù)據(jù)類型:
1.所有基本數(shù)據(jù)類型(int,float,boolean,byte,double,char,long,short)
2.String類型
3.Class類型
4.enum類型
5.Annotation類型
6.以上所有類型的數(shù)組
- 參數(shù)定義要點(diǎn)
1.只能用public或默認(rèn)(default)這兩個(gè)訪問權(quán)修飾;
2.參數(shù)成員只能用基本類型byte,short,char,int,long,float,double,boolean八種基本數(shù)據(jù)類型和 String,Enum,Class,annotations等數(shù)據(jù)類型,以及這一些類型的數(shù)組;
3.如果只有一個(gè)參數(shù)成員,建議參數(shù)名稱設(shè)為value();
4.注解元素必須有確定的值,要么在定義注解的默認(rèn)值中指定,要么在使用注解時(shí)指定,非基本類型的注解元素的值不可為null。因此, 使用空字符串或負(fù)數(shù)作為默認(rèn)值是一種常用的做法。
- 簡(jiǎn)單的自定義注解實(shí)例:
七、解析注解信息
Java使用Annotation接口來代表程序元素前面的注解,該接口是所有Annotation類型的父接口。相應(yīng)地,Java在java.lang.reflect 包下新增了AnnotatedElement接口,該接口代表程序中可以接受注解的程序元素。
實(shí)際上,java.lang.reflect 包所有提供的反射API擴(kuò)充了讀取運(yùn)行時(shí)Annotation信息的能力。當(dāng)一個(gè)Annotation類型被定義為運(yùn)行時(shí)的Annotation后,該注解才能是運(yùn)行時(shí)可見,當(dāng)class文件被裝載時(shí)被保存在class文件中的Annotation才會(huì)被虛擬機(jī)讀取。
AnnotatedElement接口是所有程序元素(Field、Method、Package、Class和Constructor)的父接口,所以程序通過反射獲取了某個(gè)類的AnnotatedElement對(duì)象之后,程序就可以調(diào)用該對(duì)象的如下七個(gè)方法來訪問Annotation信息:
1.?T getAnnotation(Class?annotationClass) :返回該程序元素上存在的、指定類型的注解,如果該類型注解不存在,則返回null;
2.Annotation[] getDeclaredAnnotation(Class):返回該程序元素上存在的、指定類型的注解,如果該類型注解不存在,則返回null;與此接口中的其他方法不同,該方法將忽略繼承的注解;
3.Annotation[] getAnnotations():返回該程序元素上存在的所有注解;
4.Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注釋。與此接口中的其他方法不同,該方法將忽略繼承的注解;
5.Annotation[] getAnnotationsByType(Class):返回直接存在于此元素上指定注解類型的所有注解;
6.Annotation[] getDeclaredAnnotationsByType(Class):返回直接存在于此元素上指定注解類型的所有注解。與此接口中的其他方法不同,該方法將忽略繼承的注解;
7.boolean isAnnotationPresent(Class<?extends Annotation> annotationClass):判斷該程序元素上是否包含指定類型的注解,存在則返回true,否則返回false;
/***********注解聲明***************/ /*** 水果名稱注解*/ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FruitName {String value() default " "; }/*** 水果顏色注解*/ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FruitColor {/*** 顏色枚舉*/public enum Color{BLUE, RED, GREEN};/*** 顏色屬性* @return*/Color fruitColor() default Color.GREEN; }/*** 水果供應(yīng)商注解*/ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FruitProvider {/*** 供應(yīng)商編號(hào)* @return*/public int id() default -1;/*** 供應(yīng)商名稱* @return*/public String name() default " ";/*** 供應(yīng)商地址* @return*/public String address() default " "; } /***********注解使用***************/ public class Apple {@FruitName("Apple")private String appleName;@FruitColor(fruitColor = FruitColor.Color.RED)private String appleColor;@FruitProvider(id = 1, name = "陜西紅富士集團(tuán)", address = "陜西紅富士大廈")private String appleProvider;public String getAppleProvider() {return appleProvider;}public void setAppleProvider(String appleProvider) {this.appleProvider = appleProvider;}public String getAppleName() {return appleName;}public void setAppleName(String appleName) {this.appleName = appleName;}public String getAppleColor() {return appleColor;}public void setAppleColor(String appleColor) {this.appleColor = appleColor;}public void displayName(){System.out.println(getAppleName());} }/***********注解信息獲取***************/ public class AnnotationParser {public static void main(String[] args) {Field[] fields = Apple.class.getDeclaredFields();for (Field field : fields) {//System.out.println(field.getName().toString());if (field.isAnnotationPresent(FruitName.class)){FruitName fruitName = field.getAnnotation(FruitName.class);System.out.println("水果的名稱:" + fruitName.value());}else if (field.isAnnotationPresent(FruitColor.class)){FruitColor fruitColor = field.getAnnotation(FruitColor.class);System.out.println("水果的顏色:"+fruitColor.fruitColor());}else if (field.isAnnotationPresent(FruitProvider.class)){FruitProvider fruitProvider = field.getAnnotation(FruitProvider.class);System.out.println("水果供應(yīng)商編號(hào):" + fruitProvider.id() + " 名稱:" + fruitProvider.name() + " 地址:" + fruitProvider.address());}}} }/***********輸出結(jié)果***************/ 水果的名稱:Apple 水果的顏色:RED 水果供應(yīng)商編號(hào):1 名稱:陜西紅富士集團(tuán) 地址:陜西紅富士大廈八、JDK8注解新特性
JDK 8 主要有兩點(diǎn)改進(jìn):類型注解和重復(fù)注解
- 類型注解
類型注解在@Target中增加了兩個(gè)ElementType參數(shù):
1.ElementType.TYPE_PARAMETER 表示該注解能寫在類型變量的聲明語(yǔ)句中;
2.ElementType.TYPE_USE 表示該注解能寫在使用類型的任何語(yǔ)句中(例如聲明語(yǔ)句、泛型和強(qiáng)制轉(zhuǎn)換語(yǔ)句中的類型);
從而擴(kuò)展了注解使用的范圍,可以使用在創(chuàng)建類實(shí)例、類型映射、implements語(yǔ)句、throw exception聲明中的類型前面。例如:
1.創(chuàng)建類實(shí)例
new @Interned MyObject();
2.類型映射
myString = (@NonNull String) str;
3.implements 語(yǔ)句中
class UnmodifiableList?implements @Readonly List<@Readonly T> { ... }
4.throw exception聲明
void monitorTemperature() throws @Critical TemperatureException { … }
簡(jiǎn)單示例:
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) public @interface Encrypted { }public class MyTypeAnnotation {@Encrypted String data;List<@Encrypted String> strings; }類型注解的作用:
首先,局域變量聲明中的類型注解也可以保留在類文件中,完整泛型被保留,并且在運(yùn)行期可以訪問,從而有助于我們獲取更多的代碼信息;其次,類型注解可以支持在的程序中做強(qiáng)類型檢查。配合第三方工具check framework,可以在編譯的時(shí)候檢測(cè)出runtime error,以提高代碼質(zhì)量;最后,代碼中包含的注解清楚表明了編寫者的意圖,使代碼更具有表達(dá)意義,有助于閱讀者理解程序,畢竟代碼才是“最根本”的文檔、“最基本”的注釋。
- 重復(fù)注解
重復(fù)注釋就是運(yùn)行在同一元素上多次使用同一注解,使用@Repeatable注解。
之前也有重復(fù)使用注解的解決方案,但可讀性不是很好,例如:
public @interface Authority {String role(); }public @interface Authorities {Authority[] value(); }public class RepeatAnnotationUseOldVersion { @Authorities({@Authority(role="Admin"),@Authority(role="Manager")})public void doSomeThing(){} }而現(xiàn)在的實(shí)現(xiàn)如下:
@Repeatable(Authorities.class) public @interface Authority {String role(); }public @interface Authorities {Authority[] value(); }public class RepeatAnnotationUseNewVersion {@Authority(role="Admin")@Authority(role="Manager")public void doSomeThing(){ } }不同的地方是,創(chuàng)建重復(fù)注解Authority時(shí),加上@Repeatable,指向存儲(chǔ)注解Authorities,在使用時(shí)候,直接可以重復(fù)使用Authority注解。從上面例子看出,java 8里面做法更適合常規(guī)的思維,可讀性強(qiáng)一點(diǎn)。
總結(jié)
本篇主要從Annotation的概念、作用、分類進(jìn)行了大概的介紹,然后通過對(duì)系統(tǒng)內(nèi)置注解、元注解、自定義注解、解析注解信息等四個(gè)方面逐步以代碼實(shí)例的方式展開對(duì)注解認(rèn)識(shí)和使用,最后講了JDK8中新添加的類型注解和重復(fù)注解,從而對(duì)Java 注解有了更系統(tǒng)化的認(rèn)識(shí)。從jdk5引入注解以來,我們看到了注解在javadoc文檔、JUnit單元測(cè)試、Spring依賴配置等各方面蓬勃發(fā)展,而現(xiàn)在jdk8更是大大拓展了注解的使用范圍,為新的設(shè)計(jì)和工具帶來了更多的機(jī)遇,讓我們拭目以待。希望本篇對(duì)剛?cè)腴T或想對(duì)注解有個(gè)全面回顧的小伙伴有幫助,更多關(guān)于Java的精彩內(nèi)容,敬請(qǐng)關(guān)注DevinBlog
參考文章:
Java Annotations
深入理解Java注解
Java8新特性探究
from:?http://zhangchuzhao.site/2016/09/23/java-annotation/
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的Java 注解深入理解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 深入理解Java:注解(Annotati
- 下一篇: Java异常的栈轨迹(Stack Tra