一、Annotation的簡單介紹
1、什麼是Annotation?
Annotation其實就是代碼裏的一種特殊標記,這些標記可以在編譯、類加載、運行時被讀取,並執行相應的處理。
2、Annotation的好處:
通過使用Annotation,程序開發人員可以在不改變原有邏輯的情況下,在源文件中嵌入一些補充信息。
JDK1.5新特性中的Annotation也增加了Java對元數據(MetaData)的支持。
3、Annotation的特點:
Annotation可以像修飾符一樣被使用,可以用於修飾包、類、構造方法、一般方法、成員變量、局部變量以及參數的聲明。
4、Annotation的基本使用
使用Annotation時需要在其前面加上“@”符號,並把該Annotation當成一個修飾符使用,用於修飾它支持的程序元素。
5、系統內建三個的Annotation:
(Annotation定義在java.lang包噹噹中,因爲此包在使用時是自動導入的,所以可以直接使用內建的3個Annotation)
@Override:限定重寫父類方法,該註解只能用於方法。
@Deprecated:用於表示某個程序元素(類、方法、變量等等)已經過時。
@SuppressWarnings:抑制編譯器的安全警告。
代碼示例:
package cn.itcast.day2;
public class AnnotationTest {
@SuppressWarnings("deprecation") //該Annotation標識表示忽略抑制編譯器的警告。
public static void main(String[]args) {
System.runFinalizersOnExit(true);
}
@Deprecated //該Annotation標識表示此方法已經過時。
public static void sayHello(){
System.out.println("hello world");
}
@Override //該Annotation標識表示次方法是重寫了父類的方法。
public String toString(){
return "";
}
}
二、系統內建的Annotation介紹
1、@Override
作用:主要是在方法覆寫時使用,用於保證方法覆寫的正確性(即保證方法是否被正確覆寫)。
@Override代碼示例:
package com.hy.test;
class Person{
public String getInfo(){
return "person run";
}
}
class Student extends Person{ //Student類繼承Person類
@Override //此處明確標識出方法覆寫的操作。
public String getInfo(){
return "student run ...";
}
}
public class OverrideAnnotationDemo {
public static void main(String[]args) {
//輸出信息。
System.out.println(newStudent().getInfo());
//輸出結果:student run …
}
}
如果覆寫方法錯誤,則在編譯時會出現以下錯誤:
因爲@Override是標識方法覆寫的正確性,而子類中的getinfo()和父類中的getInfo()方法是兩個不同的方法,不是真正的方法覆寫,所以在編譯時會出現以上錯誤提示。
注意:@Override在使用時只能在方法上使用。
2、@Deprecated
作用:主要功能是用來聲明一個已過期的方法(即不建議使用的方法)。
@Deprecated代碼示例:
package com.hy.test;
class Demo{
@Deprecated //標識聲明該方法已經過期,不建議使用。
public String getInfo(){
return "hello java";
}
}
public class DeprecatedAnnotationDemo {
public static void main(String[]args) {
System.out.println(new Demo().getInfo());
}
}
注意:如果在程序中使用了此方法,則在編譯時會出現警告信息。雖然有警告信息,但是程序依然可以正常運行,該方法可以繼續使用。
還可以根據警告信息裏面提示的命令參數來獲取已過時的方法以及位置。
3、SuppressWarnings
作用:主要功能是用來抑制編譯器的的警告。
代碼示例1:抑制單個警告
package com.hy.test;
public class AnnotationTest {
@SuppressWarnings("deprecation") //單個警告:抑制劃橫線的方法已過時提示的警告。
public static void main(String[] args) {
System.runFinalizersOnExit(true);
}
}
注意:
如果要同時抑制多個警告的話,則可以在@SuppressWarnings後面通過字符串數組的形式增加多個註釋關鍵字進行聲明。
代碼示例2:抑制多個警告
package com.hy.test;
@Deprecated
class Test<T>{ //定義泛型類,並標識此類已經過期。
private T var;
public T getVar() {
return var;
}
public void setVar(T var) {
this.var = var;
}
}
public class AnnotationTest {
@SuppressWarnings({"unchecked","deprecation"}) //同時抑制兩條警告。
public static void main(String[]args) {
Test t = new Test();
t.setVar("hello world");
System.out.println(t.getVar());
}
}
注意:
在設置註釋信息的時候,是以“key-value”的形式出現的。所以也可以在@SuppressWarnings時也可以直接使用“value={"unchecked","deprecation"}”的方式設置。
代碼示例3:@SuppressWarnings的另一種設置方式
package com.hy.test;
@Deprecated
class Test2<T>{
private T var;
public T getVar() {
return var;
}
public void setVar(T var) {
this.var = var;
}
}
public class AnnotationTest2 {
@SuppressWarnings(value={"unchecked","deprecation"}) //另外一種同時抑制兩種警告的方式。
public static void main(String[]args) {
Test2 t2 = new Test2();
t2.setVar("hello java");
System.out.println(t2.getVar());
}
}
三、自定義Annotation
1、自定義Annotation的聲明格式:
[public] @interface Annotation名稱{
數據類型變量名();
}
注意:
①、在自定義Annotation時也可以定義各種變量,但是變量後面必須要加上一對小括號()。
②、使用@interface聲明Annotation接口,實際上就是相當於繼承了java.lang.annotation.Annotation接口。
代碼示例:自定義Annotation
package com.hy.test;
public @interface MyAnnotationNoneMethod{ //自定義Annotation
}
@MyAnnotationNoneMethod //使用自定義的Annotation
class Demo{
}
2、自定義帶有參數的Annotation
Java內建的3種Annotation中的@SuppressWarnings註釋中可以傳遞參數,而自定義的Annotation中也可以根據聲明格式中的變量屬性傳遞參數。
代碼示例1:帶一個參數的自定義Annotation
package com.hy.test;
public @interface MyAnnotationSingleParam{
public String value(); //記住:此處的變量名必須是value,寫成其它的則會提示報錯。
}
@MyAnnotationSingleParam("java") //帶一個參數的自定義Annotation
class Demo{
}
代碼示例2:帶一個參數的自定義Annotation另一種設置方式
package com.hy.test;
public @interface MyAnnotationSingleParam{
public String value();
}
@MyAnnotationSingleParam(value="java") //另外一種設置方式。此種方式一般用於帶多個參數時纔會使用。
class Demo{
}
代碼示例3:帶多個參數的自定義Annotation
package com.hy.test;
public @interface MyAnnotationMoreParam{
public String key(); //變量的名稱必須要爲key或者value
public String value();
}
@MyAnnotationMoreParam(key="hello",value="java")
class Demo{
}
注意:
①、如果想要爲一個Annotation變量屬性設置多個內容,則必須將該變量屬性定義成一個數組。
②、如果Annotation的屬性只有一個值,則在使用該Annotation的時候可以省略屬性名(即:Annotation的變量名)不寫。
代碼示例4:使自定義的Annotation單個參數屬性有多個內容
package com.hy.test;
public @interface MyAnnotationArrayParam{
public String[] value(); //接收設置的內容是一個字符串數組。
}
//使自定義的單個參數屬性有多個內容。
@MyAnnotationArrayParam(value={"hello","java","123"})
class Demo{
}
3、設置自定義Annotation的默認值
當自定義了一個帶單個或者多個參數屬性的Annotation時,如果在使用該Annotation時沒有傳入相應的參數內容,則在編譯時候會出現報錯。
代碼示例:
package com.hy.test;
public @interface MyAnnotationDefaultValue{
//該自定義Annotation內定義了兩個變量屬性。
public String key();
public String value();
}
@MyAnnotationDefaultValue //該類使用Annotation時沒有指定具體屬性內容。
class Demo{
}
class MyAnnotationDefaultValueDemo{
public static void main(String[]args) {
new Demo();
}
}
錯誤提示:
Exception in thread "main" java.lang.Error: Unresolved compilationproblems:
Thepublic type MyAnnotationDefaultValue must be defined in its own file
Theannotation @MyAnnotationDefaultValue must define the attribute key
Theannotation @MyAnnotationDefaultValue must define the attribute value
atcom.hy.test.Demo4.<init>(MyAnnotationDefaultValueDemo.java:3)
atcom.hy.test.MyAnnotationDefaultValueDemo.main(MyAnnotationDefaultValueDemo.java:15)
結論:以上錯誤提示表明在定義Annotation時設置了屬性,則在使用時就必須設置屬性內容。
設置Annotation默認值的作用:
爲了方便用戶使用,可以在定義Annotation變量屬性時指定其默認值,避免因爲使用Annotation沒有設置屬性內容而出現報錯。
設置Annotation默認值的聲明格式:
[public] @interface Annotation名稱{
數據類型變量名() default默認值;
}
代碼示例:設置Annotation默認值
package com.hy.test;
public @interface MyAnnotationDefaultValue{
public String key() default "hello"; //設置變量屬性的默認值內容。
public String value() default "java"; //設置變量屬性的默認值內容。
}
@MyAnnotationDefaultValue //此處沒有設置屬性內容。
class Demo4 {
}
4、使用枚舉限制Annotation設置的內容
在Annotation中可以通過枚舉來限定Annotation的參數屬性的取值範圍,即Annotation參數屬性的內容必須爲枚舉類中定義的元素內容。
代碼示例:
package com.hy.test;
enum Technology{ //定義枚舉類。
JAVA,JSP,HTML; //定義枚舉類中的元素內容。
}
public @interface MyAnnotationEnum{
//設置默認值,且默認值必須爲枚舉中定義的取值內容。
public Technology tech() default Technology.JAVA;
}
@MyAnnotationEnum(tech=Technology.JSP) //使用自定義的Annotation,傳入參數內容,參數內容爲枚舉類中的指定元素內容。
class Demo5{
}
5、Retention和RetentionPolicy
Retention的作用:在Annotation中,可以通過Retention定義定義一個Annotation的保存範圍。
Retention的源碼定義:
package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public@interfaceRetention {
/**
* Returns theretention policy.
* @return theretention policy
*/
RetentionPolicy value();
}
RetentionPolicy的作用:Retention的源碼中存在一個RetentionPolicy類型的變量,此變量用於指定Annotation的保存範圍。
RetentionPolicy的3個範圍:
序 號 |
範 圍 |
描 述 |
1 |
SOURCE |
此Annotation類型的信息只會保留在程序源文件(.java)中,編譯之後不會保存在編譯好的字節碼文件(.class)中。 |
2 |
CLASS |
此Annotation類型的信息將保留在程序源文件和編譯之後的字節碼文件當中。在使用此類時,這些Annotation信息將不會被加載到JVM中去,如果一個Annotation聲明時沒有指定範圍,則默認是此範圍。 |
3 |
RUNTIME |
此Annotation類型的信息保留在源文件和編譯之後的字節碼文件當中,在執行時也會加載到JVM中去。 |
Java內建的3個Annotation的保存範圍:
@Override:定義採用的是@Retention(value=SOURCE),只會保存在程序源文件當中。
@Deprecated:定義採用的是@Retention(value=RUNTIME),保留在源文件和字節碼文件當中,在執行時也會被加載進JVM。
@SuppressWarnings:定義採用的是@Retention(value=SOURCE),只會保存在程序源文件當中。
代碼示例:定義在RUNTIME範圍內有效的Annotation
package com.hy.test;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
enum Tech{
JAVA,JSP,HTML;
}
@Retention(value=RetentionPolicy.RUNTIME) //此Annotation在執行時會被加載進JVM
public @interface MyDefaultRetentionAnnotation{
public Tech value() default Tech.JAVA; //設置Annotation的默認值爲枚舉中的內容。
}
@MyDefaultRetentionAnnotation(value=Tech.HTML)
class Demo{
}
注意:
當Annotation定義在RUNTIME範圍時,在程序執行時起作用,但是如果此時將其設置成其它Retention範圍(CLASS或者SOURCE),則以後在Annotation的應用中肯定是無法訪問到的。
要解決這個問題,則必須結合Java中的反射機制。
四、Annotation與反射的調用
1、Class類中關於Annotation的常用方法:
<A extends Annotation> A getAnnotation(Class<A> annotationClass):獲取某一個元素上存在的全部註釋。
Annotation[] getAnnotations():獲取元素上的所有註釋。
Annotation[] getDeclaredAnnotations():獲取直接存在於此元素上的所有Annotation
boolean isAnnotation():判斷該Class對象是否是一個Annotation類型,如果是則返回true,否則返回false。
boolean isAnnotationPresent(Class<?extends Annotation> annotationClass):判斷一個元素上是否存在註釋Annotation,如果是則返回true,否則返回false。
代碼示例:獲取元素上全部的Annotation
package com.hy.test;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
class Demo{
@Deprecated
@Override
@SuppressWarnings("all")
public String toString(){
return "Hello Java";
}
}
public class GetAnnotations {
public static void main(String[]args) throws Exception {
Class c = Class.forName("com.hy.test.Demo");
Method method = c.getMethod("toString");
Annotation[] annotation = method.getAnnotations();
for(Annotation an : annotation){
System.out.println(an);
}
}
}
運行結果是:@java.lang.Deprecated()
問題:明明定義了3個Annotation,爲什麼輸出結果只獲取到1個?
因爲Demo類中的toString()方法上雖然使用了3個Java內建的Annotation註釋,但是其中只有@Deprecated使用了RUNTIME的方式聲明,其它兩個都是SOURCE方式,所以運行結果只取得一個。
其實,以上方法是獲取到了一個元素中全部使用了RUNTIME方式所聲明的Annotation。
2、通過反射獲取指定的Annotation
爲了獲取到某個指定的Annotation,所以在獲取之前需要進行明確的判斷,使用Class類中的isAnnotationPresent()方法實現。
代碼示例:獲取指定的Annotation中的內容
//自定義一個使用RUNTIME方式聲明的Annotation並且在類中使用該自定義Annotation
package com.hy.test;
importjava.lang.annotation.Retention;
importjava.lang.annotation.RetentionPolicy;
//定義一個使用RUNTIME方式聲明的Annotation
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotationReflect {
public String key() default "zhangsan"; //設置key的默認值。
public String value() default "20"; //設置value的默認值。
}
class Demo8{
@Override
@Deprecated
@SuppressWarnings("all")
@MyAnnotationReflect(key="lisi",value="28") //使用自定義的Annotation並設置兩個屬性參數的值。
public String toString(){ //覆寫toString()方法。
return "Hello JAVA";
}
}
//獲取指定的Annotation中的屬性值。
package com.hy.test;
import java.lang.reflect.Method;
public class AnnotationReflect{
public static void main(String[]args) throws Exception{
//獲取Class類的實例。
Class c = Class.forName("com.hy.test.Demo8");
//獲取toString()方法。
Method method = c.getMethod("toString");
//判斷自定義的Annotation上是否存在註釋。
if(method.isAnnotationPresent(MyAnnotationReflect.class)){
//聲明自定義Annotation的對象。
MyAnnotationReflect mar = method.getAnnotation(MyAnnotationReflect.class);
//獲取自定義Annotation中指定變量參數的內容。
String key = mar.key();
Stringvalue = mar.value();
//輸出獲取到的Annotation變量參數的內容。
System.out.println("key="+key);
System.out.println("value="+value);
}
}
}
輸出結果:key=lisi
value=28
五、其它常用註釋
1、@Target註釋
①、作用:如果一個Annotation沒有明確的指定使用的位置,則可以在任意的位置使用(例如:可在類、方法…上面使用),而如果要指定一個Annotation的使用位置,則必須通過@Target註釋。
代碼示例:Annotation可以在任意位置使用
package com.hy.test;
//自定義Annotation,並指定屬性變量初始值。
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotationReflect {
public String key() default "zhangsan";
public String value() default "20";
}
@MyAnnotationReflect(key="lisi",value="22")
public class SimpleBean {
//使用自定義的Annotation,並設置兩個屬性參數的內容。
@MyAnnotationReflect(key="wangwu",value="25")
public String toString(){
return "Hello Java";
}
}
②、@Target註釋的源碼定義:
package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interfaceTarget {
ElementType[] value();
}
注意:在Target註釋當中存在一個ElementType[]枚舉類型的變量,這個變量的主要作用是限制Annotation的使用位置。
③、ElementType類型限制Annotation的使用範圍
序號 |
使用位置(範圍) |
解釋說明 |
1 |
public static final ElementType TYPE |
只能在類、接口、枚舉上聲明使用 |
2 |
public static final ElementType ANNOTATION_TYPE |
只能在註釋的聲明上使用 |
3 |
public static final ElementType CONSTRUCTOR |
只能在構造方法的聲明上使用 |
4 |
public static final ElementType FIELD |
只能在字段(包括枚舉常量)的聲明上使用 |
5 |
public static final ElementType METHOD |
只能在方法的聲明上使用 |
6 |
public static final ElementType PARAMETER |
只能在參數的聲明上使用 |
7 |
public static final ElementType LOCAL_VARIABLE |
只能在局部變量的聲明上使用 |
8 |
public static final ElementType PACKAGE |
只能在包的聲明上使用 |
代碼示例1:限定自定義的Annotation只能在類上使用
package com.hy.test;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE) //使用@Target註釋明確指定該Annotation只能在類上使用。
@Retention(value=RetentionPolicy.RUNTIME)
public @interface MyTargetAnnotation{
public String key() default "JAVA"; //定義變量以及默認值。
public String value() default "Android";
}
@MyTargetAnnotation(key="PHP",value="HTML") //在類上使用自定義的Annotation。
classTargetAnnotationTest{
//在方法上使用自定義的Annotation,會報錯,因爲Annotation中使用@Target註釋明確限定使用的位置是在類上。
@MyTargetAnnotation(key="JAVAEE",value="Hadoop")
public String sayHello(){
return "Hello JAVA & Android";
}
}
注意:
從@Target註釋的源碼定義當中可以發現其中可以接收一個ElementType枚舉類型的數組,也就是說可以指定Annotation同時使用在多個位置上。
代碼示例2:指定自定義Annotation同時使用在多個位置上
package com.hy.test;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE,ElementType.METHOD}) //通過在@Target註釋中傳入一個ElementType類型的數組,可以允許該自定義Annotation同時在類的多個位置上使用。
@Retention(value=RetentionPolicy.RUNTIME)
public @interface MyTargetAnnotation{
public String key() default "JAVA";
public String value() default "Android";
}
@MyTargetAnnotation(key="PHP",value="HTML") //在類上使用自定義的Annotation。
classTargetAnnotationTest{
//在方法上使用自定義的Annotation。
@MyTargetAnnotation(key="JAVA EE",value="Hadoop")
public String sayHello(){
return "Hello JAVA & Android";
}
}
2、@Documented註釋
①、作用:在生成javadoc文檔的時候可以通過@Documented註釋將一些文檔的說明信息寫入進去,編譯用戶瞭解類或者方法等的作用。
注:其實任何一個自定義的Annotation實際上都是通過@Documented進行註釋的。
②、@Documented註釋的使用格式:
[public] @interface Annotation名稱{
數據類型變量名();
}
代碼示例:對方法進行javadoc註釋,並生成.html格式的說明文檔
MyDocumentedAnnotation.java
package com.hy.test;
import java.lang.annotation.Documented;
//自定義Annotation,使用@Documented註釋。
@Documented
public @interface MyDocumentedAnnotation {
public String key();
public String value();
}
MyDocumentedAnnotationTest.java
package com.hy.test;
@MyDocumentedAnnotation(key="JAVA",value="HTML")
public class MyDocumentedAnnotationTest {
/**
* 此方法在對象輸出時調用,返回對象的信息
*/
@MyDocumentedAnnotation(key="JAVA",value="Android")
public String toString(){
return "Hello World";
}
}
然後在DOS命令行下面使用javadoc.exe這個工具的來生成程序說明文檔,執行以下語句:
javadoc –dc:\document MyDocumentedAnnotationTest.java
執行成功之後,就會在C:\document 目錄下生成javadoc文檔,如下圖所示:
直接打開document文件夾當中的index.html就可以看到剛剛編寫的javadoc說明信息,如下圖:
3、Inherited註釋
①、作用:@Inherited註釋用於標註一個父類的註釋是否可以被子類所繼承。如果一個Annotation需要被其子類所繼承,則在聲明時直接使用@Inherited註釋即可。
②、@Inherited註釋的源碼定義:
package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interfaceInherited {
}
代碼示例:驗證子類繼承父類的Annotation,使用@Inherited註釋
//自定義一個使用@Inherited註釋的Annotation
MyInheritedAnnotation.java
package com.hy.test;
import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Inherited //此自定義註釋可以被子類繼承。
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyIneritedAnnotation {
public String name();
}
//定義一個Persons類,再定義一個子類繼承Persons類。
Persons.java
package com.hy.test;
//定義父類Persons,使用自定義的Annotation
@MyIneritedAnnotation(name="zhangsan")
class Persons {}
//定義子類繼承Persons
class Students extends Persons{}
//通過反射的方式驗證父類上使用的Annotation是否被子類繼承
MyIneritedDemo.java
package com.hy.test;
importjava.lang.annotation.Annotation;
public class MyInheritedDemo {
public static void main(String[]args) throws Exception {
//獲取Class類實例對象。
Class c = Class.forName("com.hy.test.Students");
//獲取全部的Annotation。
Annotation[] annotation = c.getAnnotations();
for(Annotation an : annotation){
System.out.println(an); //輸出獲取到的所有Annotation
}
if(c.isAnnotationPresent(MyIneritedAnnotation.class)){
//獲取自定義的Annotation,且此Annotation是從父類繼承過來的。
MyIneritedAnnotation mia = (MyIneritedAnnotation) c.getAnnotation(MyIneritedAnnotation.class);
String name = mia.name(); //獲取自定義Annotation中的指定變量的內容。
System.out.println("name = "+name);
}
}
}
輸出結果:
@com.hy.test.MyIneritedAnnotation(name=zhangsan)
name = zhangsan
結論:如果Annotation沒有使用@Inherited註釋,則此Annotation是無法被子類所繼承的。