先看一個(gè)常用注解的小例子:
public class Demo{
//單一注解
@Test
public static void A(){
System.out.println("Test");
}
}
//一個(gè)方法上多個(gè)注解
@Deprecated
@SuppressWarnings("uncheck")
public static void B(){
}
通過在方法上使用@Test注解后,在運(yùn)行該方法時(shí),測(cè)試框架會(huì)自動(dòng)識(shí)別該方法并單獨(dú)調(diào)用,@Test實(shí)際上是一種標(biāo)記注解欧穴,起標(biāo)記作用,運(yùn)行時(shí)告訴測(cè)試框架該方法為測(cè)試方法诱咏。而對(duì)于@Deprecated和@SuppressWarnings(“uncheck”)苔可,則是Java本身內(nèi)置的注解,在代碼中袋狞,可以經(jīng)撤俑ǎ看見它們,但這并不是一件好事苟鸯,畢竟當(dāng)方法或是類上面有@Deprecated注解時(shí)同蜻,說明該方法或是類都已經(jīng)過期不建議再用,@SuppressWarnings 則表示忽略指定警告早处,比如@SuppressWarnings(“uncheck”)湾蔓,這就是注解的最簡(jiǎn)單的使用方式.
基本語法:
-
聲明注解和元注解
我們先來看看前面的Test注解是如何聲明的:
注意:Test內(nèi)部沒有定義其他元素,表示此注解為--標(biāo)記注解(markder annotation)
//聲明Test注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
//內(nèi)部沒有定義其他元素砌梆,表示此注解為標(biāo)記注解(markder annotation)
}
我們使用了@interface聲明了Test注解默责,并使用@Target注解傳入ElementType.METHOD參數(shù)來標(biāo)明@Test只能用于方法上,@Retention(RetentionPolicy.RUNTIME)則用來表示該注解生存期是運(yùn)行時(shí)咸包,從代碼上看注解的定義很像接口的定義桃序,確實(shí)如此,畢竟在編譯后也會(huì)生成Test.class文件烂瘫。對(duì)于@Target和@Retention是由Java提供的元注解媒熊,所謂元注解就是標(biāo)記其他注解的注解
@Target 用來約束注解可以應(yīng)用的地方(如方法、類或字段)坟比,其中ElementType是枚舉類型芦鳍,其定義如下,也代表可能的取值范圍
public enum ElementType{
TYPE,//標(biāo)明該注解可以用于類葛账、接口(包括注解類型)或enum聲明
FIELD,//標(biāo)明該注解可以用于字段(域)聲明柠衅,包括enum實(shí)例
METHOD,//標(biāo)明該注解可以用于方法聲明
PARAMETER,//標(biāo)明該注解可以用于參數(shù)聲明
CONSTRUCTOR,//標(biāo)明注解可以用于構(gòu)造函數(shù)聲明
LOCAL_VARIABLE,//標(biāo)明注解可以用于局部變量聲明
ANNOTATION_TYPE,//標(biāo)明注解可以用于注解聲明(應(yīng)用于另一個(gè)注解上)
PACKAGE,//標(biāo)明注解可以用于包聲明
/**
** @since 1.8 類型使用聲明,1.8新加入
**
/
TYPE_PARAMETER,
/**
** @since 1.8 類型使用聲明籍琳,1.8新加入
**
/
TYPE_USE
如果@Target沒有指定具體的值的時(shí)候茄茁,可以綁定多個(gè)枚舉字段,例如:
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
@Retention用來約束注解的生命周期巩割,分別有三個(gè)值裙顽,源碼級(jí)別(source),類文件級(jí)別(class)或者運(yùn)行時(shí)級(jí)別(runtime)宣谈,其含有如下:
? ? SOURCE:注解將被編譯器丟棄(該類型的注解信息只會(huì)保留在源碼里愈犹,源碼經(jīng)過編譯后,注解信息會(huì)被丟棄,不會(huì)保留在編譯好的class文件里)
? ?CLASS:注解在class文件中可用漩怎,但會(huì)被VM丟棄(該類型的注解信息會(huì)保留在源碼里和class文件里勋颖,在執(zhí)行的時(shí)候,不會(huì)加載到虛擬機(jī)中)勋锤,請(qǐng)注意饭玲,當(dāng)注解未定義Retention值時(shí),默認(rèn)值是CLASS叁执,如Java內(nèi)置注解茄厘,@Override、@Deprecated谈宛、@SuppressWarnning等
? ?RUNTIME:注解信息將在運(yùn)行期(JVM)也保留次哈,因此可以通過反射機(jī)制讀取注解的信息(源碼、class文件和執(zhí)行的時(shí)候都有注解的信息)吆录,如SpringMvc中的@Controller窑滞、@Autowired、@RequestMapping等恢筝。
-
注解元素及其數(shù)據(jù)類型
通過上述對(duì)@Test注解的定義哀卫,我們了解了注解定義的過程,由于@Test內(nèi)部沒有定義其他元素撬槽,所以@Test也稱為標(biāo)記注解(marker annotation)聊训,但在自定義注解中,一般都會(huì)包含一些元素以表示某些值恢氯,方便處理器使用,這點(diǎn)在下面的例子將會(huì)看到:
/**
* Created by wuzejian on 2017/5/18.
* 對(duì)應(yīng)數(shù)據(jù)表注解
*/
@Target(ElementType.TYPE)//只能應(yīng)用于類上
@Retention(RetentionPolicy.RUNTIME)//保存到運(yùn)行時(shí)
public @interface DBTable { String name() default "";
}
上述定義一個(gè)名為DBTable的注解鼓寺,該用于主要用于數(shù)據(jù)庫(kù)表與Bean類的映射(稍后會(huì)有完整案例分析)勋拟,與前面Test注解不同的是,我們聲明一個(gè)String類型的name元素妈候,其默認(rèn)值為空字符敢靡,但是必須注意到對(duì)應(yīng)任何元素的聲明應(yīng)采用方法的聲明方式,同時(shí)可選擇使用default提供默認(rèn)值苦银,@DBTable使用方式如下:
//在類上使用該注解
@DBTable(name = "MEMBER")
public class Member {
//.......
}
關(guān)于注解支持的元素?cái)?shù)據(jù)類型除了上述的String啸胧,還支持如下數(shù)據(jù)類型
- 所有基本類型(int,float,boolean,byte,double,char,long,short)
- String
- Class
- enum
- Annotation
- 上述類型的數(shù)組
切記:
- 倘若使用了其他數(shù)據(jù)類型,編譯器將會(huì)丟出一個(gè)編譯錯(cuò)誤
- 聲明注解元素時(shí)可以使用基本類型但不允許使用任何包裝類型
- 注解也可以作為元素的類型幔虏,也就是嵌套注解纺念,下面的代碼演示了上述類型的使用過程:
package com.zejian.annotationdemo;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Reference{ boolean next() default false; }
public @interface AnnotationElementDemo {
//枚舉類型
enum Status {FIXED,NORMAL};
//聲明枚舉
Status status() default Status.FIXED;
//布爾類型
boolean showSupport() default false;
//String類型
String name()default "";
//class類型
Class<?> testCase() default Void.class;
//注解嵌套
Reference reference() default @Reference(next=true);
//數(shù)組類型 long[] value(); }
-
編譯器對(duì)默認(rèn)值的限制
其次,對(duì)于非基本類型的元素想括,無論是在源代碼中聲明陷谱,還是在注解接口中定義默認(rèn)值,都不能以null作為值,這就是限制烟逊,沒有什么利用可言渣窜,但造成一個(gè)元素的存在或缺失狀態(tài),因?yàn)槊總€(gè)注解的聲明中宪躯,所有的元素都存在乔宿,并且都具有相應(yīng)的值,為了繞開這個(gè)限制访雪,只能定義一些特殊的值详瑞,例如空字符串或負(fù)數(shù),表示某個(gè)元素不存在冬阳。
-
注解不支持繼承
注解是不支持繼承的蛤虐,因此不能使用關(guān)鍵字extends來繼承某個(gè)@interface,但注解在編譯后肝陪,編譯器會(huì)自動(dòng)繼承java.lang.annotation.Annotation接口驳庭,這里我們反編譯前面定義的DBTable注解
package com.zejian.annotationdemo;
import java.lang.annotation.Annotation;
//反編譯后的代碼
public interface DBTable extends Annotation{
public abstract String name();
}
雖然反編譯后發(fā)現(xiàn)DBTable注解繼承了Annotation接口,請(qǐng)記住氯窍,即使Java的接口可以實(shí)現(xiàn)多繼承饲常,但定義注解時(shí)依然無法使用extends關(guān)鍵字繼承@interface。
-
快捷方式(一般不推薦)
所謂的快捷方式就是注解中定義了名為value的元素狼讨,并且在使用該注解時(shí)贝淤,如果該元素是唯一需要賦值的一個(gè)元素,那么此時(shí)無需使用key=value的語法政供,而只需在括號(hào)內(nèi)給出value元素所需的值即可播聪。這可以應(yīng)用于任何合法類型的元素,記住布隔,這限制了元素名必須為value离陶,簡(jiǎn)單案例如下:
package com.zejian.annotationdemo;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//定義注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface IntegerVaule{
int value() default 0; String name() default "";
}
//使用注解 public class QuicklyWay {
//當(dāng)只想給value賦值時(shí),可以使用以下快捷方式
@IntegerVaule(20) public int age;
//當(dāng)name也需要賦值時(shí)必須采用key=value的方式賦值
@IntegerVaule(value = 10000,name = "MONEY")
public int money;
}
-
Java內(nèi)置注解與其它元注解
接著看看Java提供的內(nèi)置注解,主要有3個(gè)衅檀,如下:
- @Override:用于標(biāo)明此方法覆蓋了父類的方法招刨,源碼如下
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
- @Deprecated:用于標(biāo)明已經(jīng)過時(shí)的方法或類,源碼如下哀军,關(guān)于@Documented稍后分析
@Documented @Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
- @SuppressWarnnings:用于有選擇的關(guān)閉編譯器對(duì)類沉眶、方法、成員變量杉适、變量初始化的警告谎倔,其實(shí)現(xiàn)源碼如下:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
其內(nèi)部有一個(gè)String數(shù)組,主要接收值如下:
deprecation:使用了不贊成使用的類或方法時(shí)的警告猿推; unchecked:執(zhí)行了未檢查的轉(zhuǎn)換時(shí)的警告传藏,例如當(dāng)使用集合時(shí)沒有用泛型 (Generics) 來指定集合保存的類型; fallthrough:當(dāng) Switch 程序塊直接通往下一種情況而沒有 Break 時(shí)的警告; path:在類路徑、源文件路徑等中有不存在的路徑時(shí)的警告; serial:當(dāng)在可序列化的類上缺少 serialVersionUID 定義時(shí)的警告; finally:任何 finally 子句不能正常完成時(shí)的警告; all:關(guān)于以上所有情況的警告。
這個(gè)三個(gè)注解比較簡(jiǎn)單毯侦,看個(gè)簡(jiǎn)單案例即可:
//注明該類已過時(shí)哭靖,不建議使用
@Deprecated
class A{ public void A(){ }
//注明該方法已過時(shí),不建議使用 @Deprecated() public void B(){ } }
class B extends A{
@Override //標(biāo)明覆蓋父類A的A方法
public void A() { super.A(); }
//去掉檢測(cè)警告 @SuppressWarnings({"uncheck","deprecation"})
public void C(){ }
//去掉檢測(cè)警告 @SuppressWarnings("uncheck")
public void D(){ } }
-
前面我們分析了兩種元注解侈离,@Target和@Retention试幽,除了這兩種元注解,Java還提供了另外兩種元注解卦碾,@Documented和@Inherited铺坞,下面分別介紹:
- Documented 被修飾的注解會(huì)生成到j(luò)avadoc中
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DocumentA { }
//沒有使用@Documented @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME)
public @interface DocumentB { }
//使用注解 @DocumentA @DocumentB
public class DocumentDemo { public void A(){ } }
使用javadoc命令生成文檔:
JasonMacBook$ javadoc DocumentDemo.java DocumentA.java DocumentB.java
- @Inherited 可以讓注解被繼承,但這并不是真的繼承洲胖,只是通過使用@Inherited济榨,可以讓子類Class對(duì)象使用getAnnotations()獲取父類被@Inherited修飾的注解,如下:
@Inherited
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DocumentA { } @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DocumentB { }
@DocumentA
class A{ }
class B extends A{ }
@DocumentB class C{ }
class D extends C{ }
//測(cè)試
public class DocumentDemo {
public static void main(String... args){ A instanceA=new B();
System.out.println("已使用的@Inherited注解:"+Arrays.toString(instanceA.getClass().getAnnotations()));
C instanceC = new D();
System.out.println("沒有使用的@Inherited注解:"+Arrays.toString(instanceC.getClass().getAnnotations())); }