元數(shù)據(jù)的作用
如果要對(duì)于元數(shù)據(jù)的作用進(jìn)行分類,,目前還沒有明確的定義,,不過我們可以根據(jù)它所起的作用,大致可分為三類:
l 編寫文檔:通過代碼里標(biāo)識(shí)的元數(shù)據(jù)生成文檔。
l 代碼分析:通過代碼里標(biāo)識(shí)的元數(shù)據(jù)對(duì)代碼進(jìn)行分析。
l 編譯檢查:通過代碼里標(biāo)識(shí)的元數(shù)據(jù)讓編譯器能實(shí)現(xiàn)基本的編譯檢查,。
基本內(nèi)置注釋
@Override 注釋能實(shí)現(xiàn)編譯時(shí)檢查,你可以為你的方法添加該注釋,,以聲明該方法是用于覆蓋父類中的方法,。如果該方法不是覆蓋父類的方法,將會(huì)在編譯時(shí)報(bào)錯(cuò),。例如我們?yōu)槟愁愔貙?span lang=EN-US>toString() 方法卻寫成了tostring() ,,并且我們?yōu)樵摲椒ㄌ砑恿?span lang=EN-US>@Override 注釋;
@Deprecated 的作用是對(duì)不應(yīng)該在使用的方法添加注釋,,當(dāng)編程人員使用這些方法時(shí),,將會(huì)在編譯時(shí)顯示提示信息,它與javadoc 里的 @deprecated 標(biāo)記有相同的功能,準(zhǔn)確的說,,它還不如javadoc @deprecated ,,因?yàn)樗恢С謪?shù),
注意:要了解詳細(xì)信息,,請(qǐng)使用 -Xlint:deprecation 重新編譯,。
@SuppressWarnings 與前兩個(gè)注釋有所不同,你需要添加一個(gè)參數(shù)才能正確使用,,這些參數(shù)值都是已經(jīng)定義好了的,我們選擇性的使用就好了,,參數(shù)如下:
deprecation 使用了過時(shí)的類或方法時(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)于以上所有情況的警告
注意:要了解詳細(xì)信息,,請(qǐng)使用 -Xlint:unchecked 重新編譯,。
定制注釋類型
好的,讓我們創(chuàng)建一個(gè)自己的注釋類型(annotation type )吧,。它類似于新創(chuàng)建一個(gè)接口類文件,,但為了區(qū)分,我們需要將它聲明為@interface, 如下例:
public @interface NewAnnotation {
}
使用定制的注釋類型
我們已經(jīng)成功地創(chuàng)建好一個(gè)注釋類型NewAnnotation ,,現(xiàn)在讓我們來嘗試使用它吧,,如果你還記得本文的第一部分,那你應(yīng)該知道他是一個(gè)標(biāo)記注釋,,使用也很容易,,如下例:
public class AnnotationTest {
@NewAnnotation
public static void main(String[] args) {
}
}
添加變量
J2SE 5.0 里,我們了解到內(nèi)置注釋@SuppressWarnings() 是可以使用參數(shù)的,,那么自定義注釋能不能定義參數(shù)個(gè)數(shù)和類型呢,?答案是當(dāng)然可以,但參數(shù)類型只允許為基本類型,、String ,、Class 、枚舉類型等,,并且參數(shù)不能為空,。我們來擴(kuò)展NewAnnotation ,為之添加一個(gè)String 類型的參數(shù),,示例代碼如下:
public @interface NewAnnotation {
String value();
}
使用該注釋的代碼如下:正如你所看到的,,該注釋的使用有兩種寫法,這也是在之前的文章里所提到過的,。如果你忘了這是怎么回事,,那就再去翻翻吧。
public class AnnotationTest {
@NewAnnotation("Just a Test.")
public static void main(String[] args) {
sayHello();
}
@NewAnnotation(value="Hello NUMEN.")
public static void sayHello() {
// do something
}
}
為變量賦默認(rèn)值
我們對(duì)Java 自定義注釋的了解正在不斷的增多,,不過我們還需要更過,,在該條目里我們將了解到如何為變量設(shè)置默認(rèn)值,,我們?cè)賹?duì)NewAnnotaion 進(jìn)行修改,看看它會(huì)變成什么樣子,,不僅參數(shù)多了幾個(gè),,連類名也變了。但還是很容易理解的,,我們先定義一個(gè)枚舉類型,,然后將參數(shù)設(shè)置為該枚舉類型,并賦予默認(rèn)值,。
public @interface Greeting {
public enum FontColor {RED, GREEN, BLUE};
String name();
String content();
FontColor fontColor() default FontColor.BLUE;
}
限定注釋使用范圍
當(dāng)我們的自定義注釋不斷的增多也比較復(fù)雜時(shí),,就會(huì)導(dǎo)致有些開發(fā)人員使用錯(cuò)誤,主要表現(xiàn)在不該使用該注釋的地方使用,。為此,,Java 提供了一個(gè)ElementType 枚舉類型來控制每個(gè)注釋的使用范圍,比如說某些注釋只能用于普通方法,,而不能用于構(gòu)造函數(shù)等,。下面是Java 定義的ElementType 枚舉:
package java.lang.annotation;
public enum ElementType {
TYPE, // Class, interface, or enum (but not annotation)
FIELD, // Field (including enumerated values)
METHOD, // Method (does not include constructors)
PARAMETER, // Method parameter
CONSTRUCTOR, // Constructor
LOCAL_VARIABLE, // Local variable or catch clause
ANNOTATION_TYPE, // Annotation Types (meta-annotations)
PACKAGE // Java package
}
下面我們來修改Greeting 注釋,為之添加限定范圍的語(yǔ)句,,這里我們稱它為目標(biāo)(Target )使用方法也很簡(jiǎn)單,,如下:
@Target( { ElementType.METHOD, ElementType.CONSTRUCTOR })
public @interface Greeting {
}
正如上面代碼所展示的,我們只允許Greeting 注釋標(biāo)注在普通方法和構(gòu)造函數(shù)上,,使用在包申明,、類名等時(shí),會(huì)提示錯(cuò)誤信息,。
注釋保持性策略
public enum RetentionPolicy {
SOURCE,// Annotation is discarded by the compiler
CLASS,// Annotation is stored in the class file, but ignored by the VM
RUNTIME// Annotation is stored in the class file and read by the VM
}
RetentionPolicy 的使用方法與ElementType 類似,,簡(jiǎn)單代碼示例如下:
@Retention(RetentionPolicy.RUNTIME)
@Target( { ElementType.METHOD, ElementType.CONSTRUCTOR })
文檔化功能
Java 提供的Documented 元注釋跟Javadoc 的作用是差不多的,其實(shí)它存在的好處是開發(fā)人員可以定制Javadoc 不支持的文檔屬性,,并在開發(fā)中應(yīng)用,。它的使用跟前兩個(gè)也是一樣的,簡(jiǎn)單代碼示例如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target( { ElementType.METHOD, ElementType.CONSTRUCTOR })
public @interface Greeting {
}
值得大家注意的是,,如果你要使用@Documented 元注釋,,你就得為該注釋設(shè)置RetentionPolicy.RUNTIME 保持性策略。為什么這樣做,,應(yīng)該比較容易理解,,這里就不提了。
標(biāo)注繼承
繼承應(yīng)該是Java 提供的最復(fù)雜的一個(gè)元注釋了,,它的作用是控制注釋是否會(huì)影響到子類,,簡(jiǎn)單代碼示例如下:
@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target( { ElementType.METHOD, ElementType.CONSTRUCTOR })
public @interface Greeting {
}
讀取注釋信息
當(dāng)我們想讀取某個(gè)注釋信息時(shí),我們是在運(yùn)行時(shí)通過反射來實(shí)現(xiàn)的,如果你對(duì)元注釋還有點(diǎn)印象,,那你應(yīng)該記得我們需要將保持性策略設(shè)置為RUNTIME ,,也就是說只有注釋標(biāo)記了@Retention(RetentionPolicy.RUNTIME) 的,我們才能通過反射來獲得相關(guān)信息,,下面的例子我們將沿用前面幾篇文章中出現(xiàn)的代碼,,并實(shí)現(xiàn)讀取AnnotationTest 類所有方法標(biāo)記的注釋并打印到控制臺(tái)。好了,,我們來看看是如何實(shí)現(xiàn)的吧:
public class AnnotationIntro {
public static void main(String[] args) throws Exception {
Method[] methods = Class.forName(
"com.gelc.annotation.demo.customize.AnnotationTest")
.getDeclaredMethods();
Annotation[] annotations;
for (Method method : methods) {
annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(method.getName() + " : "
+ annotation.annotationType().getName());
}
********************************************************************************
Annotation(注解)
Annotation對(duì)于程序運(yùn)行沒有影響,它的目的在于對(duì)編譯器或分析工具說明程序的某些信息,您可以
在包,類,方法,域成員等加上Annotation.每一個(gè)Annotation對(duì)應(yīng)于一個(gè)實(shí)際的Annotation類型.
1 限定Override父類方法@Override
java.lang.Override是J2SE5.0中標(biāo)準(zhǔn)的Annotation類型之一,它對(duì)編譯器說明某個(gè)方法必須
是重寫父類中的方法.編譯器得知這項(xiàng)信息后,在編譯程序時(shí)如果發(fā)現(xiàn)被@Override標(biāo)示的方法
并非重寫父類中的方法,就會(huì)報(bào)告錯(cuò)誤.
例,如果在定義新類時(shí)想要重寫Object類的toString()方法,可能會(huì)寫成這樣:
public class CustomClass{
public String ToString(){
return "customObject";
}
}
在編寫toString()方法時(shí),因?yàn)檩斎脲e(cuò)誤或其他的疏忽,將之寫成ToString()了,編譯這個(gè)類時(shí)
并不會(huì)出現(xiàn)任何的錯(cuò)誤,編譯器不會(huì)知道您是想重寫toString()方法,只會(huì)以為是定義了一個(gè)新
的ToString()方法.
可以使用java.lang.Override這個(gè)Annotation類型,在方法上加一個(gè)@Override的Annotation
這可以告訴編譯器現(xiàn)在定義的這個(gè)方法,必須是重寫父類中的同包方法.
public class CustomClass{
@Override
public String toString(){
return "coustomObject";
}
}
java.lang.Override是一個(gè)Marker Annotation,簡(jiǎn)單地說就是用于標(biāo)示的Annotation,,Annotation
名稱本身表示了要給工具程序的信息。
Annotation類型與Annotation實(shí)際上是有區(qū)分的,,Annotation是Annotation類型的實(shí)例,,例如
@Override是個(gè)Annotation,它是java.lang.Override類型的一個(gè)實(shí)例,,一個(gè)文件中可以有很多
個(gè)@Override,但它們都是屬于java.lang.Override類型,。
2 標(biāo)示方法為Deprecated @Deprecated
java.lang.Deprecated也是J2SE5.0中標(biāo)準(zhǔn)的Annotation類型之一,。它對(duì)編譯器說明某個(gè)方法已經(jīng)不
建議使用。如果有開發(fā)人員試圖使用或重寫被@Deprecated標(biāo)示的方法,,編譯器必須提出警告信息,。
例:
public class Something{
@Deprecated
public Something getSomething(){
return new Something();
}
}
如果有人試圖在繼承這個(gè)類后重寫getSomething()方法,或是在程序中調(diào)用getSomething()方法,,
則編譯時(shí)會(huì)有警告出現(xiàn),。
java.lang.Deprecated也是一個(gè)Marker Annotation簡(jiǎn)單地說就是用于標(biāo)示。
3 抑制編譯器警告 @SuppressWarnings
java.lang.SuppressWarnings也是J2SE5.0中標(biāo)準(zhǔn)的Annotation類型之一,,它對(duì)編譯器說明某個(gè)方法
中若有警告信息,,則加以抑制,不用在編譯完成后出現(xiàn)警告,。
例:
public class SomeClass2{
@SuppressWarnings(value={"unchecked"});
public void doSomething(){
Map map = new HashMap();
map.put("some","thing");
}
}
這樣,,編譯器將忽略u(píng)nchecked的警告,您也可以指定忽略多個(gè)警告:
@SuppressWarnings(value={"unchecked","deprecation"});
@SuppressWarnings是所謂的Single-Value Annotation,,因?yàn)檫@樣的Annotation只有一個(gè)成員,,稱為
value成員,可在使用Annotation時(shí)作額外的信息指定,。
自定義Annotation類型
可以自定義Annotation類型,,并使用這些自定義的Annotation類型在程序代碼中使用Annotation,這些
Annotation將提供信息給程序代碼分析工具,。
首先來看看如何定義Marker Annotation,,也就是Annotation名稱本身即提供信息。對(duì)于程序分析工具來
說,主要是檢查是否有Marker Annotation的出現(xiàn),并做出對(duì)應(yīng)的動(dòng)作,。要定義一個(gè)Annotation所需的動(dòng)作
,就類似于定義一個(gè)接口,,只不過使用的是@interface。
例:
public @interface Debug{}
由于是一個(gè)Marker Annotation,,所以沒有任何成員在Annotation定義中,。編譯完成后,就可以在程序代碼
中使用這個(gè)Annotation,。
public class SomeObject{
@Debug
public void doSomething(){
......
}
}
稍后可以看到如何在Java程序中取得Annotation信息(因?yàn)橐褂肑ava程序取得信息,,所以還要設(shè)置
meta-annotation,稍后會(huì)談到)
接著來看看如何定義一個(gè)Single-Value Annotation,它只有一個(gè)Value成員,。
例:
public @interface UnitTest{
String value();
}
實(shí)際上定義了value()方法,,編譯器在編譯時(shí)會(huì)自動(dòng)產(chǎn)生一個(gè)value的域成員,接著在使用UnitTest
Annotation時(shí)要指定值。如:
public class MathTool{
@UnitTest("GCD")
public static int gcdOf(int num1,int num2){
...............
}
}
@UnitTest("GCD")實(shí)際上是@UnitTest(value="GCD")的簡(jiǎn)便寫法,value也可以是數(shù)組值,。如:
public @interface FunctionTest{
String[] value();
}
在使用時(shí),,可以寫成@FunctionTest({"method1","method2"})這樣的簡(jiǎn)便形式?;蚴?br> @FunctionTest(value={"method1","method2"})這樣的詳細(xì)形式.
也可以對(duì)value成員設(shè)置默認(rèn)值,使用default關(guān)鍵詞即可,。
例:
public @interface UnitTest2{
String value() default "noMethod";
}
這樣如果使用@UnitTest2時(shí)沒有指定value值,則value默認(rèn)就是noMethod.
也可以為Annotation定義額外的成員,,以提供額外的信息給分析工具,,如:
public @interface Process{
public enum Current{NONE,REQUIRE,ANALYSIS,DESIGN,SYSTEM};
Current current() default Current.NONE;
String tester();
boolean ok();
}
運(yùn)用:
public class Application{
@process(
current = Process.Current.ANALYSIS,
tester = "Justin Lin",
ok = true
)
public void doSomething(){
...........
}
}
當(dāng)使用@interface自行定義Annotation類型時(shí),實(shí)際上是自動(dòng)繼承了
java.lang.annotation接口,,并由編譯器自動(dòng)完成其他產(chǎn)生的細(xì)節(jié),,并且在定義Annotation類型時(shí),
不能繼承其他的Annotation類型或接口.
定義Annotation類型時(shí)也可以使用包機(jī)制來管理類。由于范例所設(shè)置的包都是onlyfun.caterpillar,
所以可以直接使用Annotation類型名稱而不指定包名,,但如果是在別的包下使用這些自定義的Annotation
,,記得使用import告訴編譯器類型的包們置。
如:
import onlyfun.caterpillar.Debug;
public class Test{
@Debug
public void doTest(){
......
}
}
或是使用完整的Annotation名稱.如:
public class Test{
@onlyfun.caterpillar.Debug
public void doTest(){
......
}
}
meta-annotation
所謂neta-annotation就是Annotation類型的數(shù)據(jù),,也就是Annotation類型的Annotation,。在定義
Annotation類型時(shí),為Annotation類型加上Annotation并不奇怪,,這可以為處理Annotation類型
的分析工具提供更多的信息,。
1 告知編譯器如何處理annotation @Retention
java.lang.annotation.Retention類型可以在您定義Annotation類型時(shí),指示編譯器該如何對(duì)待自定
義的Annotation類型,,編譯器默認(rèn)會(huì)將Annotation信息留在.class文件中,,但不被虛擬機(jī)讀取,而僅用
于編譯器或工具程序運(yùn)行時(shí)提供信息,。
在使用Retention類型時(shí),,需要提供java.lang.annotation.RetentionPolicy的枚舉類型,。
RetentionPolicy的定義如下所示:
package java.lang.annotation;
public enum RetentionPolicy{
SOURCE,//編譯器處理完Annotation信息后就沒有事了
CLASS,//編譯器將Annotation存儲(chǔ)于class文件中,默認(rèn)
RUNTIME //編譯器將Annotation存儲(chǔ)于class文件中,,可由VM讀入
}
RetentionPolicy為SOURCE的例子是@SuppressWarnings,這個(gè)信息的作用僅在編譯時(shí)期告知
編譯器來抑制警告,,所以不必將這個(gè)信息存儲(chǔ)在.class文件中。
RetentionPolicy為RUNTIME的時(shí)機(jī),,可以像是您使用Java設(shè)計(jì)一個(gè)程序代碼分析工具,,您必須讓VM能讀出
Annotation信息,以便在分析程序時(shí)使用,,搭配反射機(jī)制,,就可以達(dá)到這個(gè)目的。\
J2SE6.0的java.lang.reflect.AnnotatedElement接口中定義有4個(gè)方法:
public Annotation getAnnotation(Class annotationType)
public Annotation[] getAnnotations();
public Annotation[] getDeclaredAnnotations()
public boolean isAnnotationPresent(Class annotationType);
Class,Constructor,field,Method,Package等類,,都實(shí)現(xiàn)了AnnotatedElement接口,,所以可以從這些
類的實(shí)例上,分別取得標(biāo)示于其上的Annotation與相關(guān)信息,。由于在執(zhí)行時(shí)讀取Annotation信息,,所以定
義Annotation時(shí)必須設(shè)置RetentionPolicy為RUNTIME,也就是可以在VM中讀取Annotation信息。
例:
package onlyfun.caterpillar;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPllicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface SomeAnnotation{
String value();
String name();
}
由于RetentionPolicy為RUNTIME,,編譯器在處理SomeAnnotation時(shí),,會(huì)將Annotation及給定的相關(guān)信息
編譯至.class文件中,并設(shè)置為VM可以讀出Annotation信息,。接下來:
package onlyfun.caterpillar;
public class SomeClass3{
@SomeAnotation{
value="annotation value1",
name="annotation name1"
}
public void doSomething(){
......
}
}
現(xiàn)在假設(shè)要設(shè)計(jì)一個(gè)源代碼分析工具來分析所設(shè)計(jì)的類,一些分析時(shí)所需的信息已經(jīng)使用Annotation標(biāo)示于類
中了,,可以在執(zhí)行時(shí)讀取這些Annotation的相關(guān)信息,。例:
package onlyfun.caterpillar;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
public class AnalysisApp{
public static void main(String [] args) throws NoSuchMethodException{
Class<SomeClass3> c = SomeClass3.class;
//因?yàn)镾omeAnnotation標(biāo)示于doSomething()方法上
//所以要取得doSomething()方法的Method實(shí)例
Method method = c.getMethod("doSomething");
//如果SomeAnnotation存在
if(method.isAnnotationPresent(SomeAnnotation.class){
System.out.println("找到@SomeAnnotation");
//取得SomeAnnotation
SomeAnnotation annotation = method.getAnnotation(SomeAnnotation.class);
//取得vlaue成員值
System.out.println(annotation.value);
//取得name成員值
System.out.println(annotation.name());
}else{
System.out.println("找不到@SomeAnnotation");
}
//取得doSomething()方法上所有的Annotation
Annotation[] annotations = method.getAnnotations();
//顯示Annotation名稱
for(Annotation annotation : annotations){
System.out.println("Annotation名稱:"+annotation.annotationType().getName());
}
}
}
若Annotation標(biāo)示于方法上,就要取得方法的Method代表實(shí)例,,同樣的,,如果Annotation標(biāo)示于類或包上,
就要分別取得類的Class代表的實(shí)例或是包的Package代表的實(shí)例。之后可以使用實(shí)例上的getAnnotation()
等相關(guān)方法,,以測(cè)試是否可取得Annotation或進(jìn)行其他操作,。
2 限定annotation使用對(duì)象 @Target
在定義Annotation類型時(shí),使用java.lang.annotation.Target可以定義其適用的時(shí)機(jī),,在定義時(shí)要指定
java.lang.annotation.ElementType的枚舉值之一,。
public enum elementType{
TYPE,//適用class,interface,enum
FIELD,//適用于field
METHOD,//適用于method
PARAMETER,//適用method上之parameter
CONSTRUCTOR,//適用constructor
LOCAL_VARIABLE,//適用于區(qū)域變量
ANNOTATION_TYPE,//適用于annotation類型
PACKAGE,//適用于package
}
舉例,假設(shè)定義Annotation類型時(shí),,要限定它只能適用于構(gòu)造函數(shù)與方法成員,,則:
package onlyfun.caterpillar;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
@Target({ElementType.CONSTRUCTOR,ElementType.METHOD})
public @interface MethodAnnotation{}
將MethodAnnotation標(biāo)示于方法之上,如:
public class SomeoneClass{
@onlyfun.caterpillar.MethodAnnotation
public void doSomething(){
......
}
}
3 要求為API文件的一部分 @Documented
在制作Java Doc文件時(shí),,并不會(huì)默認(rèn)將Annotation的數(shù)據(jù)加入到文件中.Annnotation用于標(biāo)示程序代碼以便
分析工具使用相關(guān)信息,,有時(shí)Annotation包括了重要的信息,,您也許會(huì)想要在用戶制作Java Doc文件的同時(shí),
也一并將Annotation的信息加入到API文件中。所以在定義Annotation類型時(shí),,可以使用
java.lang.annotation.Documented.例:
package onlyfun.caterpillar;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface TwoAnnotation{}
使用java.lang.annotation.Documented為定義的Annotation類型加上Annotation時(shí),,必須同時(shí)使用Retention
來指定編譯器將信息加入.class文件,并可以由VM讀取,,也就是要設(shè)置RetentionPolicy為RUNTIME,。接著可以使
用這個(gè)Annotation,并產(chǎn)生Java Doc文件,這樣可以看到文件中包括了@TwoAnnotation的信息.
4 子類是否可以繼承父類的annotation @Inherited
在定義Annotation類型并使用于程序代碼上后,,默認(rèn)父類中的Annotation并不會(huì)被繼承到子類中,。可以在定義
Annotation類型時(shí)加上java.lang.annotation.Inherited類型的Annotation,,這讓您定義的Annotation類型在
被繼承后仍可以保留至子類中,。
例:
package onlyfun.caterpillar;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Inherited;
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface ThreeAnnotation{
String value();
String name();
}
可以在下面的程序中使用@ThreeAnnotation:
public class SomeoneClass{
@onlyfun.caterpillar.ThreeAnnotation(
value = "unit",
name = "debug1"
)
public void doSomething(){
.....
}
}
如果有一個(gè)類繼承了SomeoneClass類,則@ThreeAnnotation也會(huì)被繼承下來,。