一、什么是注解
???????注解也叫元數(shù)據(jù),例如我們常見的@Override和@Deprecated,注解是JDK1.5版本開始引入的一個(gè)特性,用于對(duì)代碼進(jìn)行說明,可以對(duì)包、類、接口、字段、方法參數(shù)、局部變量等進(jìn)行注解
一般常用的注解可以分為三類:
- 一類是Java自帶的標(biāo)準(zhǔn)注解,包括@Override(標(biāo)明重寫某個(gè)方法)、@Deprecated(標(biāo)明某個(gè)類或方法過時(shí))和@SuppressWarnings(標(biāo)明要忽略的警告),使用這些注解后編譯器就會(huì)進(jìn)行檢查
- 一類為元注解,元注解是用于定義注解的注解,包括@Retention(標(biāo)明注解被保留的階段)、@Target(標(biāo)明注解使用的范圍)、@Inherited(標(biāo)明注解可繼承)、@Documented(標(biāo)明是否生成javadoc文檔)
- 一類為自定義注解,可以根據(jù)自己的需求定義注解
二、注解的用途
- 生成文檔,通過代碼里標(biāo)識(shí)的元數(shù)據(jù)生成javadoc文檔。
- 編譯檢查,通過代碼里標(biāo)識(shí)的元數(shù)據(jù)讓編譯器在編譯期間進(jìn)行檢查驗(yàn)證。
- 編譯時(shí)動(dòng)態(tài)處理,編譯時(shí)通過代碼里標(biāo)識(shí)的元數(shù)據(jù)動(dòng)態(tài)處理,例如動(dòng)態(tài)生成代碼。
- 運(yùn)行時(shí)動(dòng)態(tài)處理,運(yùn)行時(shí)通過代碼里標(biāo)識(shí)的元數(shù)據(jù)動(dòng)態(tài)處理,例如使用反射注入實(shí)例
三、注解的使用
1、Java自帶的標(biāo)準(zhǔn)注解
常見標(biāo)準(zhǔn)的Annotation:
1.)Override
??????? java.lang.Override是一個(gè)標(biāo)記類型注解,它被用作標(biāo)注方法。它說明了被標(biāo)注的方法重載了父類的方法,起到了斷言的作用。如果我們使用了這種注解在一個(gè)沒有覆蓋父類方法的方法時(shí),java編譯器將以一個(gè)編譯錯(cuò)誤來警示。
2.)Deprecated
???????Deprecated也是一種標(biāo)記類型注解。當(dāng)一個(gè)類型或者類型成員使用@Deprecated修飾的話,編譯器將不鼓勵(lì)使用這個(gè)被標(biāo)注的程序元素。所以使用這種修飾具有一定的“延續(xù)性”:如果我們?cè)诖a中通過繼承或者覆蓋的方式使用了這個(gè)過時(shí)的類型或者成員,雖然繼承或者覆蓋后的類型或者成員并不是被聲明為@Deprecated,但編譯器仍然要報(bào)警。
3.)SuppressWarnings
???????SuppressWarning不是一個(gè)標(biāo)記類型注解。它有一個(gè)類型為String[]的成員,這個(gè)成員的值為被禁止的警告名。對(duì)于javac編譯器來講,被-Xlint選項(xiàng)有效的警告名也同樣對(duì)@SuppressWarings有效,同時(shí)編譯器忽略掉無法識(shí)別的警告名。
@SuppressWarnings("unchecked")
這種注解的使用非常簡(jiǎn)單,只需在需要注解的地方標(biāo)明某個(gè)注解即可,例如在方法上注解:
public class Test {
@Override
public String tostring() {
return "override it";
}
}
例如在類上注解:
@Deprecated
public class Test {
}
2、元注解
java.lang.annotation提供了四種元注解,專門注解其他的注解(在自定義注解的時(shí)候,需要使用到元注解):
??????????????@Documented
??????????????@Retention
??????????????@Target
??????????????@Inherited
| 注解 | 作用 |
|---|---|
| @Target | 表示該注解用于什么地方。如果不明確指出,該注解可以放在任何地方 ElementType.TYPE : 用于描述類、接口或enum聲明 ElementType.FIELD : 成員變量、對(duì)象、屬性(包括enum實(shí)例) ElementType.METHOD : 用于描述方法 Element.PARAMETER : 用于描述參數(shù) Element.CONSTRUCTOR : 用于描述構(gòu)造器 Element.LOCAL_VARIABLE : 用于描述局部變量 ElementType.ANNOTATION_TYPE : 用于描述類、接口(包括注解類型) 或enum聲明 ElementType.PACKAGE : 用于描述包 |
| @Retention | 定義該注解的生命周期 RetentionPolicy.SOURCE : 在編譯階段丟棄。這些注解在編譯結(jié)束之后就不再有任何意義,所以它們不會(huì)寫入字節(jié)碼(@Override, @SuppressWarnings都屬于這類注解) RetentionPolicy.CLASS : 在類加載的時(shí)候丟棄。在字節(jié)碼文件的處理中有用。注解默認(rèn)使用這種方式 RetentionPolicy.RUNTIME : 始終不會(huì)丟棄,運(yùn)行期也保留該注解,因此可以使用反射機(jī)制讀取該注解的信息。我們自定義的注解通常使用這種方式 |
| @Document | 表示是否將注解信息添加在java文檔中 |
| @Inherited | 定義該注釋和子類的關(guān)系 |
注:如果一個(gè)使用了@Inherited修飾的annotation類型被用于一個(gè)class,則這個(gè)annotation將被用于該class的子類。@Inherited annotation類型是被標(biāo)注過的class的子類所繼承。類并不從它所實(shí)現(xiàn)的接口繼承annotation,方法并不從它所重載的方法繼承annotation
3、自定義注解
自定義注解類編寫的一些規(guī)則:
- Annotation型定義為@interface, 所有的Annotation會(huì)自動(dòng)繼承java.lang.Annotation這一接口,并且不能再去繼承別的類或是接口.
- 參數(shù)成員只能用public或默認(rèn)(default)這兩個(gè)訪問權(quán)修飾
- 參數(shù)成員只能用基本類型byte,short,char,int,long,float,double,boolean八種基本數(shù)據(jù)類型和String、Enum、Class、annotations等數(shù)據(jù)類型,以及這一些類型的數(shù)組.
- 要獲取類方法和字段的注解信息,必須通過Java的反射技術(shù)來獲取 Annotation對(duì)象,因?yàn)槟愠酥鉀]有別的獲取注解對(duì)象的方法
- 注解也可以沒有定義成員, 不過這樣注解就沒啥用了
PS:自定義注解需要使用到元注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
}
???????在注解中一般會(huì)有一些元素以表示某些值。注解的元素看起來就像接口的方法,唯一的區(qū)別在于可以為其制定默認(rèn)值。沒有元素的注解稱為標(biāo)記注解,上面的@Test就是一個(gè)標(biāo)記注解。
???????注解的可用的類型包括以下幾種:所有基本類型、String、Class、enum、Annotation、以上類型的數(shù)組形式。元素不能有不確定的值,即要么有默認(rèn)值,要么在使用注解的時(shí)候提供元素的值。而且元素不能使用null作為默認(rèn)值。注解在只有一個(gè)元素且該元素的名稱是value的情況下,在使用注解的時(shí)候可以省略“value=”,直接寫需要的值即可。
四、反射機(jī)制與注解機(jī)制
AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通過反射獲取了某個(gè)類的AnnotatedElement對(duì)象之后,程序就可以調(diào)用該對(duì)象的如下四個(gè)個(gè)方法來訪問Annotation信息:
| 方法 | 含義 |
|---|---|
| <T extends Annotation> T getAnnotation(Class<T> annotationClass) | 返回改程序元素上存在的、指定類型的注解,如果該類型注解不存在,則返回null |
| Annotation[] getAnnotations() | 返回該程序元素上存在的所有注解。 |
| boolean is AnnotationPresent(Class<?extends Annotation> annotationClass) | 判斷該程序元素上是否包含指定類型的注解,存在則返回true,否則返回false |
| Annotation[] getDeclaredAnnotations() | 返回直接存在于此元素上的所有注解。與此接口中的其他方法不同,該方法將忽略繼承的注解。(如果沒有注釋直接存在于此元素上,則返回長(zhǎng)度為零的一個(gè)數(shù)組。)該方法的調(diào)用者可以隨意修改返回的數(shù)組;這不會(huì)對(duì)其他調(diào)用者返回的數(shù)組產(chǎn)生任何影響。 |
| default <T extends Annotation> T[] getAnnotationsByType(Class <T> annotationClass) | 返回該元素制定類型的注解 |
| default <T extends Annotation> T getDeclaredAnnotation( Class <T> annotationClass) | 返回直接存在與該元素上的所有注解 |
| default <T extends Annotation>T[] getDeclaredAnntationsByType(Class <T> annotationClass) | 返回直接存在該元素上某類型的注解 |
接下來讓我們?cè)诖a中看看二者的結(jié)合
//定義一個(gè)可以注解在Class,interface,enum上的注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnTargetType {
/**
* 定義注解的一個(gè)元素 并給定默認(rèn)值
* @return
*/
String value() default "MyAnTargetType我是定義在類接口枚舉類上的注解元素value的默認(rèn)值";
}
//定義一個(gè)可以注解在PARAMETER上的注解
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnTargetParameter {
/**
* 定義注解的一個(gè)元素 并給定默認(rèn)值
* @return
*/
String value() default "MyAnTargetParameter我是定義在參數(shù)上的注解元素value的默認(rèn)值";
}
// 定義一個(gè)可以注解在METHOD上的注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnTargetMethod {
/**
* 定義注解的一個(gè)元素 并給定默認(rèn)值
*
* @return
*/
String value() default "MyAnTargetMethod我是定義在方法上的注解元素value的默認(rèn)值";
}
// 定義一個(gè)可以注解在FIELD上的注解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnTargetField {
/**
* 定義注解的一個(gè)元素 并給定默認(rèn)值
* @return
*/
String value() default "MyAnTargetField我是定義在字段上的注解元素value的默認(rèn)值";
}
//創(chuàng)建一個(gè)使用了以上四種注解的類
@MyAnTargetType
public class AnnotationTest {
@MyAnTargetField
private String field = "我是字段";
@MyAnTargetMethod("測(cè)試方法")
public void test(@MyAnTargetParameter String args) {
System.out.println("參數(shù)值 === " + args);
}
}
public class Main{
public static void main(String[] args) {
// 獲取類上的注解MyAnTargetType
MyAnTargetType t = AnnotationTest.class.getAnnotation(MyAnTargetType.class);
System.out.println("類上的注解值 === " + t.value());
MyAnTargetMethod tm = null;
try {
// 根據(jù)反射獲取AnnotationTest類上的test方法
Method method = AnnotationTest.class.getDeclaredMethod("test", String.class);
// 獲取方法上的注解MyAnTargetMethod
tm = method.getAnnotation(MyAnTargetMethod.class);
System.out.println("方法上的注解值 === " + tm.value());
// 獲取方法上的所有參數(shù)注解 循環(huán)所有注解找到MyAnTargetParameter注解
Annotation[][] annotations = method.getParameterAnnotations();
for (Annotation[] tt : annotations) {
for (Annotation t1 : tt) {
if (t1 instanceof MyAnTargetParameter) {
System.out.println("參數(shù)上的注解值 === " + ((MyAnTargetParameter) t1).value());
}
}
}
method.invoke(new AnnotationTest(), "改變默認(rèn)參數(shù)");
// 獲取AnnotationTest類上字段field的注解MyAnTargetField
MyAnTargetField fieldAn = AnnotationTest.class.getDeclaredField("field").getAnnotation(MyAnTargetField.class);
System.out.println("字段上的注解值 === " + fieldAn.value());
} catch (Exception e) {
e.printStackTrace();
}
運(yùn)行結(jié)果如下:
類上的注解值 === MyAnTargetType我是定義在類接口枚舉類上的注解元素value的默認(rèn)值
方法上的注解值 === 測(cè)試方法
參數(shù)上的注解值 === MyAnTargetParameter我是定義在參數(shù)上的注解元素value的默認(rèn)值
參數(shù)值 === 改變默認(rèn)參數(shù)
字段上的注解值 === MyAnTargetField我是定義在字段上的注解元素value的默認(rèn)值
最后再寫一個(gè)關(guān)于@Inherited例子:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MyAnTargetType {
/**
* 定義注解的一個(gè)元素 并給定默認(rèn)值
* @return
*/
String value() default "MyAnTargetType我是定義在類接口枚舉類上的注解元素value的默認(rèn)值";
}
public class Main extends AnnotationTest{
public static void main(String[] args) {
MyAnTargetType t2 = Main.class.getAnnotation(MyAnTargetType.class);
System.out.println("類上的注解值 === "+t2.value());
}
}
運(yùn)行如下:
類上的注解值 === 我是定義在類接口枚舉類上的注解元素value的默認(rèn)值
說明已經(jīng)獲取到了父類AnnotationTest的注解了
如果MyAnTargetType去掉@Inherited注解運(yùn)行則報(bào)錯(cuò)如下:
Exception in thread "main" java.lang.NullPointerException
at com.company.Main.main(Main.java:41)
最后借用下別人的Java注解的基礎(chǔ)知識(shí)點(diǎn)導(dǎo)圖

關(guān)于Java注解機(jī)制的基礎(chǔ)就講完了,歡迎大家前來diss