Java中的註解基礎
一、元註解
元註解的作用就是負責註解其他註解。
[email protected]
@Target用來指明註解所修飾的目標,包括packages、types(類、介面、列舉、Annotation型別)、型別成員(方法、構造方法、成員變數、列舉值)、方法引數和本地變數(如迴圈變數、catch引數)。
取值(ElementType)有:
CONSTRUCTOR:用於描述構造器
FIELD:用於描述域
LOCAL_VARIABLE:用於描述區域性變數
METHOD:用於描述方法
PACKAGE:用於描述包
PARAMETER:用於描述引數
TYPE:用於描述類、介面(包括註解型別) 或enum宣告
例如:
Target(ElementType.TYPE)
public @interface AnnotationTest1 {
public String tableName() default "className";
}
- 1
- 2
- 3
- 4
表示AnnotationTest1這個註解用來註解類、介面(包括註解型別) 或enum宣告。
@Target(ElementType.FIELD)
public @interface AnnotationTest2 {
}
- 1
- 2
- 3
- 4
表示註解AnnotationTest2用於註解類的成員變數。
[email protected]
@Retention定義了該Annotation的生命週期,某些Annotation僅出現在原始碼中,而被編譯器丟棄;而另一些卻被編譯在class檔案中;編譯在class檔案中的Annotation可能會被虛擬機器忽略,而另一些在class被裝載時將被讀取。
取值(RetentionPoicy)有:
SOURCE:在原始檔中有效(即原始檔保留)
CLASS:在class檔案中有效(即class保留)
RUNTIME:在執行時有效(即執行時保留)
例如:
@Target(ElementType.FIELD)
@Retention (RetentionPolicy.RUNTIME)
public @interface Column {
public String name() default "fieldName";
public String setFuncName() default "setField";
public String getFuncName() default "getField";
public boolean defaultDBValue() default false;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
表示Column這個註解用來註解類的成員變數,並且註解一直保留到執行時。
[email protected]
@Documented的作用是在生成javadoc文件的時候將該Annotation也寫入到文件中。
例如:
@Target(ElementType.METHOD)
@Documented
public @interface DocumentTest {
String hello();
}
- 1
- 2
- 3
- 4
- 5
該註解用來修飾成員方法,下面簡單的使用;
public class DocumentClass {
/**
* this is method of doSomething
*/
@DocumentTest(hello = "yahaitt")
public void doSomething() {
System.out.println("do something");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
生成的doc檔案中如下:
@DocumentTest(hello="yahaitt")
public void doSomething()
this is method of doSomething
- 1
- 2
- 3
[email protected]
我們自定義註解(Annotation)時,把自定義的註解標註在父類上,但是它不會被子類所繼承,我們可以在定義註解時給我們自定義的註解標註一個@Inherited註解來實現註解繼承。
例如:
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface InheritedTest {
String value();
}
- 1
- 2
- 3
- 4
- 5
@InheritedTest("Jadyer")
public class Parent {
@InheritedTest("JavaEE")
public void doSomething() {
System.out.println("Parent do something");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
public class Child extends Parent {
}
- 1
- 2
- 3
public class Test {
public static void main(String[] args) throws SecurityException, NoSuchMethodException {
classTest();
methodTest();
}
/**
* 通過反射方式測試子類是否會繼承父類中定義在類上面的註解
*/
public static void classTest(){
Class<Child> c = Child.class;
if (c.isAnnotationPresent(InheritedTest.class)) {
InheritedTest inheritedTest = c.getAnnotation(InheritedTest.class);
String value = inheritedTest.value();
System.out.println(value);
}
}
/**
* 通過反射方式測試子類是否會繼承父類中定義在方法上面的註解
*/
public static void methodTest() throws SecurityException, NoSuchMethodException{
Class<Child> c = Child.class;
Method method = c.getMethod("doSomething", new Class[]{});
if (method.isAnnotationPresent(InheritedTest.class)) {
InheritedTest inheritedTest = method.getAnnotation(InheritedTest.class);
String value = inheritedTest.value();
System.out.println(value);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
如果父類的註解是定義在類上面,那麼子類是可以繼承過來的
如果父類的註解定義在方法上面,那麼子類仍然可以繼承過來
如果子類重寫了父類中定義了註解的方法,那麼子類將無法繼承該方法的註解,即子類在重寫父類中被@Inherited標註的方法時,會將該方法連帶它上面的註解一併覆蓋掉
二、Java內建註解
Java提供了三種內建註解
@Override——當我們想要複寫父類中的方法時,我們需要使用該註解去告知編譯器我們想要複寫這個方法。這樣一來當父類中的方法移除或者發生更改時編譯器將提示錯誤資訊。
@Deprecated——當我們希望編譯器知道某一方法不建議使用時,我們應該使用這個註解。Java在javadoc 中推薦使用該註解,我們應該提供為什麼該方法不推薦使用以及替代的方法。
@SuppressWarnings——這個僅僅是告訴編譯器忽略特定的警告資訊,例如在泛型中使用原生資料型別。它的保留策略是SOURCE並且被編譯器丟棄。
三、自定義註解
使用@interface自定義註解時,自動繼承了java.lang.annotation.Annotation介面,由編譯程式自動完成其他細節。
定義註解格式:
public @interface 註解名 {定義體}
註解引數的可支援資料型別:
1.所有基本資料型別(int,float,boolean,byte,double,char,long,short)
2.String型別
3.Class型別
4.enum型別
5.Annotation型別
6.以上所有型別的陣列
Annotation型別裡面的引數該怎麼設定:
第一,只能用public或預設(default)這兩個訪問權修飾.例如,String value();這裡把方法設為defaul預設型別;
第二,引數成員只能用基本型別byte,short,char,int,long,float,double,boolean八種基本資料型別和 String,Enum,Class,annotations等資料型別,以及這一些型別的陣列.例如,String value();這裡的引數成員就為String;
第三,如果只有一個引數成員,最好把引數名稱設為”value”,後加小括號
第四,可以在使用default為每個引數設定一個預設值。註解元素必須有確定的值,要麼在定義註解的預設值中指定,要麼在使用註解時指定,非基本型別的註解元素的值不可為null。因此, 使用空字串或0作為預設值是一種常用的做法。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
String value() default "";
}
- 1
- 2
- 3
- 4
- 5
- 6
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitColor {
public enum Color{ BULE,RED,GREEN};
Color fruitColor() default Color.GREEN;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
public int id() default -1;
public String name() default "";
public String address() default "";
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
四、註解的使用
public class Apple {
@FruitName("Apple")
private String appleName;
@FruitColor(fruitColor=Color.RED)
private String appleColor;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
引數名只有一個,並且設定為value,在對註解使用賦值的時候可以不用加引數名,直接賦值即可。其他情況需要指定引數名和引數值。
五、註解處理
Java在java.lang.reflect 包下新增了AnnotatedElement介面,該介面主要有如下幾個實現類:
Class:類定義
Constructor:構造器定義
Field:累的成員變數定義
Method:類的方法定義
Package:類的包定義
所以,AnnotatedElement 介面是所有程式元素(Class、Method和Constructor)的父介面,程式通過反射獲取了某個類的AnnotatedElement物件之後,程式就可以呼叫該物件的如下四個個方法來訪問Annotation資訊:
//返回改程式元素上存在的、指定型別的註解,如果該型別註解不存在,則返回null。
<T extends Annotation> T getAnnotation(Class<T> annotationClass)
//返回該程式元素上存在的所有註解。
Annotation[] getAnnotations()
//判斷該程式元素上是否包含指定型別的註解,存在則返回true,否則返回false.
boolean is AnnotationPresent(Class<?extends Annotation> annotationClass)
//返回直接存在於此元素上的所有註釋。與此介面中的其他方法不同,該方法將忽略繼承的註釋。(如果沒有註釋直接存在於此元素上,則返回長度為零的一個數組。)該方法的呼叫者可以隨意修改返回的陣列;這不會對其他呼叫者返回的陣列產生任何影響。
Annotation[] getDeclaredAnnotations()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
在上面的@Inherited的講解中也舉個相應的例子,我們只需要反射得到Class、Method和Constructor,因為AnnotatedElement是Class、Method和Constructor的父介面,它們都實現了AnnotatedElement,所以,在Class、Method和Constructor中就可以使用AnnotatedElement中宣告的方法來得到註解和註解內容。