Java內建系統註解和元註解
第一節:註解(Annotation)的作用
Annotation(註解)是JDK5.0及以後版本引入的。它的作用是修飾程式元素。什麼是程式元素呢?例如:包、類、構造方法、方法、成員變數等。
註解,就是對某一事物進行添加註釋說明,會存放一些資訊,這些資訊可能對以後某個時段來說是很有用處的。
java提供了一套機制,使得我們可以對方法、類、引數、包、域以及變數等新增標註(即附上某些資訊)。且在以後某個時段通過反射將標註的資訊提取出來以供使用。
註解相當於一種標記,在程式中加了註解就等於為程式打上了某種標記。程式可以利用java的反射機制來了解你的類及各種元素上有無何種標記,針對不同的標記,就去做相應的事件。
第二節:定義註解
定義新的Annotation型別使用@interface關鍵字(在原有interface關鍵字前增加@符號)。定義一個新的Annotation型別與定義一個介面很像,例如:
public @interface Test{
}
定義完該Annotation後,就可以在程式中使用該Annotation。使用Annotation,非常類似於public、final這樣的修飾符,通常,會把Annotation另放一行,並且放在所有修飾符之前。例如:
@Test
public class MyClass{
....
}
根據註解是否包含成員變數,可以把註解分為如下兩類:
標記註解:沒有成員變數的Annotation被稱為標記。這種Annotation僅用自身的存在與否來為我們提供資訊,例如@override等。
元資料註解:包含成員變數的Annotation。因為它們可以接受更多的元資料,因此被稱為元資料Annotation。 成員以無引數的方法的形式被宣告,其方法名和返回值定義了該成員變數的名字和型別。
成員變數
Annotation只有成員變數,沒有方法。Annotation的成員變數在Annotation定義中以“無形參的方法”形式來宣告,其方法名定義了該成員變數的名字,其返回值定義了該成員變數的型別。例如:
public @interface MyTag{ string name(); int age(); }
示例中定義了2個成員變數,這2個成員變數以方法的形式來定義。
一旦在Annotation裡定義了成員變數後,使用該Annotation時就應該為該Annotation的成員變數指定值。例如:
public class Test{
@MyTag(name="紅薯",age=30)
public void info(){
......
}
}
也可以在定義Annotation的成員變數時,為其指定預設值,指定成員變數預設值使用default關鍵字。示例:
public @interface MyTag{
string name() default "我蘭";
int age() default 18;
}
如果Annotation的成員變數已經指定了預設值,使用該Annotation時可以不為這些成員變數指定值,而是直接使用預設值。例如:
public class Test{
@MyTag
public void info(){
......
}
}
如果註解只有一個成員變數,則建議取名為value,在使用時可用忽略成員名和賦值符=
第三節:基本註解
在java.lang包下,JAVA提供了5個基本註解::
-
@Override
限定重寫父類方法。對於子類中被@Override 修飾的方法,如果存在對應的被重寫的父類方法,則正確;如果不存在,則報錯。@Override 只能作用於方法,不能作用於其他程式元素。
-
@Deprecated
用於表示某個程式元素(類、方法等)已過時。如果使用了被@Deprecated修飾的類或方法等,編譯器會發出警告。
-
@SuppressWarnings
抑制編譯器警告。指示被@SuppressWarnings修飾的程式元素(以及該程式元素中的所有子元素,例如類以及該類中的方法.....)取消顯示指定的編譯器警告。例如,常見的@SuppressWarnings(value="unchecked")
SuppressWarnings註解的常見引數值的簡單說明:
1.deprecation:使用了不贊成使用的類或方法時的警告(使用@Deprecated使得編譯器產生的警告);
2.unchecked:執行了未檢查的轉換時的警告,例如當使用集合時沒有用泛型 (Generics) 來指定集合儲存的型別; 關閉編譯器警告
3.fallthrough:當 Switch 程式塊直接通往下一種情況而沒有 Break 時的警告;
4.path:在類路徑、原始檔路徑等中有不存在的路徑時的警告;
5.serial:當在可序列化的類上缺少 serialVersionUID 定義時的警告;
6.finally:任何 finally 子句不能正常完成時的警告;
7.all:關於以上所有情況的警告。
-
@SafeVarargs
@SafeVarargs是JDK 7 專門為抑制“堆汙染”警告提供的。
-
@FunctionalIterface (java 8 新增的)
函式式介面。Java8規定:如果介面中只有一個抽象方法(可以包含多個預設方法或多個static方法),該介面稱為函式式介面。
@FunctionalInterface就是用來指定某個介面必須是函式式介面,否則就會編譯出錯。
@FunctionalInterface
public interface Fun
{
static void foo()
{
System.out.println("foo類方法");
}
default void bar()
{
System.out.println("bar預設方法");
}
void test();//只定義了一個抽象方法
}
如在上面的介面中再加一個抽象方法abc(),則會編譯出錯。
第四節 元註解
在java.lang.annotation包下,定義了6個元註解。元註解就是修飾註解的註解。
拿到一個註解,如何知道它是否是元註解呢?需要看它的元註解(無論是元註解還是普通註解都是有元註解的),如果看到這樣的元註解:@Target(ElementType.ANNOTATION_TYPE),那麼此時這個註解一定是元註解。
-
@Retention
-
@Target
-
@Documented
-
@Inherited
-
@Repeatable (java 8新增)
-
型別註解
@Repetable和型別註解在另外一篇博文中介紹。
4.1 @Retention
@Retention用於指定註解可以保留多長時間(生命週期)。
@Retention包含一個名為“value”的成員變數,該value成員變數是RetentionPolicy列舉型別。使用@Retention時,必須為其value指定值。value成員變數的值只能是如下3個:
-
RetentionPolicy.SOURCE:Annotation只保留在原始碼中,編譯器編譯時,直接丟棄這種Annotation,不記錄在.class檔案中。
-
RetentionPolicy.CLASS:編譯器把Annotation記錄在class檔案中。當執行Java程式時,JVM中不可獲取該Annotation資訊。這是預設值
-
RetentionPolicy.RUNTIME:編譯器把Annotation記錄在class檔案中。當執行Java程式時,JVM可獲取該Annotation資訊,程式可以通過反射獲取該Annotation的資訊。
示例:
package com.demo1;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
//name=value形式
//@Retention(value=RetentionPolicy.RUNTIME)
//直接指定
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTag{
String name() default "我蘭";
}
如果Annotation裡有一個名為“value“的成員變數,使用該Annotation時,可以直接使用XXX(val)形式為value成員變數賦值,無須使用name=val形式。
4.2 @Target
@Target指定Annotation用於修飾哪些程式元素。@Target也包含一個名為”value“的成員變數,該value成員變數型別為ElementType[ ],ElementType為列舉型別,值有如下幾個:
-
ElementType.TYPE:能修飾類、介面或列舉型別
-
ElementType.FIELD:能修飾成員變數
-
ElementType.METHOD:能修飾方法
-
ElementType.PARAMETER:能修飾引數
-
ElementType.CONSTRUCTOR:能修飾構造器
-
ElementType.LOCAL_VARIABLE:能修飾區域性變數
-
ElementType.ANNOTATION_TYPE:能修飾註解
-
ElementType.PACKAGE:能修飾包
示例1(單個ElementType):
package com.demo1;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
public @interface AnnTest {
String name() default "sunchp";
}
示例2(多個ElementType):
package com.demo1;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target({ ElementType.FIELD, ElementType.METHOD })
public @interface AnnTest {
String name() default "sunchp";
}
4.3 @Documented
如果定義註解A時,使用了@Documented修飾定義,則在用javadoc命令生成API文件後,所有使用註解A修飾的程式元素,將會包含註解A的說明。
示例:
@Documented
public @interface Testable {
}
public class Test {
@Testable
public void info() {
}
}
4.4 @Inherited
@Inherited指定註解具有繼承性。如果某個類使用了@xxx註解(定義該註解時使用了@Inherited修飾)修飾,則其子類將自動被@xxx修飾
示例:
package com.demo2;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MyTag{
}
package com.demo2;
@MyTag
public class Base {
}
package com.demo2;
//SubClass只是繼承了Base類
//並未直接使用@MyTag註解修飾
public class SubClass extends Base {
public static void main(String[] args) {
System.out.println(SubClass.class.isAnnotationPresent(MyTag.class));
}
}
示例中Base使用@MyTag修飾,SubClass繼承Base,而且沒有直接使用@MyTag修飾,但是因為MyTag定義時,使用了@Inherited修飾,具有了繼承性,所以執行結果為true。
如果MyTag註解沒有被@Inherited修飾,則執行結果為:false。
第五節:註解的語法與定義形式
(1)以@interface關鍵字定義
(2)註解需要標明註解的生命週期,註解的修飾目標等資訊,這些資訊是通過元註解實現。
上面的語法不容易理解,下面通過例子來說明一下,這個例子就是Target註解的原始碼,
1 2 3 4 5 6 | @Retention(value=RetentionPolicy.RUNTIME) @Target(value={ElementType.ANNOTATION_TYPE}) public@interfaceTarget { ElementType[]value(); } |
原始碼分析如下:
第一:元註解@Retention,成員value的值為RetentionPolicy.RUNTIME。
第二:元註解@Target,成員value是個陣列,用{}形式賦值,值為ElementType.ANNOTATION_TYPE
第三:成員名稱為value,型別為ElementType[]
另外,需要注意一下,如果成員名稱是value,在賦值過程中可以簡寫。如果成員型別為陣列,但是隻賦值一個元素,則也可以簡寫。如上面的簡寫形式為:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
第六節:註解的底層實現
定義一個註解:
1 2 3 4 5 6 | @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public@interfaceCache { Stringvalue()default"cache"; } |
分析其位元組碼,如下圖所示:
C:\Users\Administrator\workspace\cache\bin>javap -verbose Cache
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 34 35 36 37 38 39 | Classfile/C:/Users/Administrator/workspace/cache/bin/Cache.class Last modified2016-7-23;size429bytes MD5checksum771f9c4d63ce2ded5d3b093deebd2c69 Compiled from"Cache.java" publicinterfaceCacheextendsjava.lang.annotation.Annotation minor version:0 majorversion:52 flags:ACC_PUBLIC,ACC_INTERFACE,ACC_ABSTRACT,ACC_ANNOTATION Constantpool: #1 = Class #2 // Cache #2 = Utf8 Cache #3 = Class #4 // java/lang/Object #4 = Utf8 java/lang/Object #5 = Class #6 // java/lang/annotation/Annotation #6 = Utf8 java/lang/annotation/Annotation #7 = Utf8 value #8 = Utf8 ()Ljava/lang/String; #9 = Utf8 AnnotationDefault #10 = Utf8 cache #11 = Utf8 SourceFile #12 = Utf8 Cache.java #13 = Utf8 RuntimeVisibleAnnotations #14 = Utf8 Ljava/lang/annotation/Retention; #15 = Utf8 Ljava/lang/annotation/RetentionPolicy; #16 = Utf8 RUNTIME #17 = Utf8 Ljava/lang/annotation/Target; #18 = Utf8 Ljava/lang/annotation/ElementType; #19 = Utf8 TYPE { publicabstractjava.lang.Stringvalue(); descriptor:()Ljava/lang/String; flags:ACC_PUBLIC,ACC_ABSTRACT AnnotationDefault: default_value:s#10 } SourceFile:"Cache.java" RuntimeVisibleAnnotations: 0:#14(#7=e#15.#16) 1:#17(#7=[e#18.#19]) |
分析上面的位元組碼,我們可以得出:
第一:
public interface Cache extends java.lang.annotation.Annotation,說明Cache註解是繼承自Annotation,仍然是interface。
第二:
public abstract java.lang.String value(),說明value方法是abstract型別。