|
級別: 初級
Brett McLaughlin, 作者/編者, O‘Reilly Media, Inc
2004 年 9 月 01 日
本系列文章的 第 1 部分介紹了注釋 —— J2SE 5.0 中新的元數(shù)據(jù)工具,,并重點討論了 Tiger 的基本內(nèi)置注釋,。一個更強(qiáng)大的相關(guān)特性是支持編寫自己的注釋。本文中,,Brett McLauglin 說明了如何創(chuàng)建定制注釋,,如何用自己的注釋注解文檔,并進(jìn)一步定制代碼,。
本系列的第一篇文章 介紹了什么是元數(shù)據(jù),,元數(shù)據(jù)的重要性,以及如何使用 J2SE 5.0(也叫做 Tiger)的基本內(nèi)置注釋。如果習(xí)慣了這些概念,,您可能已經(jīng)在想,,Java 5 提供的三種標(biāo)準(zhǔn)注釋也并不是特別健壯,能使用的只有 Deprecated ,、 SuppressWarnings 和 Override 而已,。所幸的是,Tiger 還允許定義自己的注釋類型,。在本文中,,我將通過一些示例引導(dǎo)您掌握這個相對簡單的過程。您還將了解如何對自己的注釋進(jìn)行注解,,以及這樣做的一些好處,。我要感謝 O‘Reilly Media, Inc.,他們非??犊卦试S我在本文中使用我關(guān)于 Tiger 的書籍的“注釋”一章中的代碼示例(請參閱 參考資料)。
定義自己的注釋類型
通過添加了一個小小的語法(Tiger 添加了大量的語法結(jié)構(gòu)),,Java 語言支持一種新的類型 —— 注釋類型(annotation type),。注釋類型看起來很像普通的類,但是有一些特有的性質(zhì),。最明顯的一點是,,可以在類中以符號( @ )的形式注釋其他 Java 代碼。我將一步一步地介紹這個過程,。
@interface 聲明
定義新的注釋類型與創(chuàng)建接口有很多類似之處,,只不過 interface 關(guān)鍵字之前要有一個 @ 符號。清單 1 中給出的是一個最簡單的注釋類型的示例: 清單 1. 非常簡單的注釋類型
package com.oreilly.tiger.ch06;
/**
* Marker annotation to indicate that a method or class
* is still in progress.
*/
public @interface InProgress { }
|
清單 1 的含義非常明顯,。如果編譯這個注釋類型,,并確信其位于類路徑中,那么您就可以在自己的源代碼方法中使用它,,以指出某個方法或類仍在處理中,,如清單 2 所示:
清單 2. 使用定制的注釋類型
@com.oreilly.tiger.ch06.InProgress
public void calculateInterest(float amount, float rate) {
// Need to finish this method later
}
|
清單 1 所示注釋類型的使用方法和內(nèi)置注釋類型的使用方法完全相同,只不過要同時使用名稱和所在的包來指示定制注釋,。當(dāng)然,,一般的 Java 規(guī)則仍然適用,您可以導(dǎo)入該注釋類型,,直接使用 @InProgress 引用它,。
添加成員
上面所示的基本用法還遠(yuǎn)遠(yuǎn)不夠健壯,。您一定還記得“第 1 部分”中曾經(jīng)提到的,注釋類型可以有成員變量(請參閱 參考資料)。這一點非常有用,,尤其是準(zhǔn)備將注釋作為更加復(fù)雜的元數(shù)據(jù),,而不僅僅將它作為原始文檔使用的時候。代碼分析工具喜歡加工大量的信息,,定制注釋可以提供這類信息,。
注釋類型中的數(shù)據(jù)成員被設(shè)置成使用有限的信息進(jìn)行工作。定義數(shù)據(jù)成員后不需要分別定義訪問和修改的方法,。相反,,只需要定義一個方法,以成員的名稱命名它,。數(shù)據(jù)類型應(yīng)該是該方法返回值的類型,。清單 3 是一個具體的示例,它澄清了一些比較含糊的要求:
清單 3. 向注釋類型添加成員
package com.oreilly.tiger.ch06;
/**
* Annotation type to indicate a task still needs to be
* completed.
*/
public @interface TODO {
String value();
}
|
盡管清單 3 看起來很奇怪,,但這是注釋類型所要求的格式,。清單 3 定義了一個名為 value 的字符串,該注釋類型能夠接受它,。然后,,就可以像清單 4 中那樣使用注釋類型:
清單 4. 使用帶有成員值的注釋類型
@com.oreilly.tiger.ch06.InProgress
@TODO("Figure out the amount of interest per month")
public void calculateInterest(float amount, float rate) {
// Need to finish this method later
}
|
這里同樣沒有多少花樣。清單 4 假設(shè)已經(jīng)引入了 com.oreilly.tiger.ch06.TODO ,,因此源代碼中的注釋 不 需要包名作前綴,。此外,需要注意的是,,清單 4 中采用了簡寫的方法:將值 ("Figure out the amount of interest per month") 直接提供給注釋,,沒有指定成員變量名。清單 4 和清單 5 是等價的,,后者沒有采用簡寫形式:
清單 5. 清單 4 的“加長”版
@com.oreilly.tiger.ch06.InProgress
@TODO(value="Figure out the amount of interest per month")
public void calculateInterest(float amount, float rate) {
// Need to finish this method later
}
|
當(dāng)然作為編碼人員,,我們都不愿意跟這種“加長”版攪在一起。不過要注意,,只有當(dāng)注釋類型只有 一個 成員變量,,而且變量名為 value 時,才能使用簡寫形式,。如果不符合這個條件,,那么就無法利用這種特性。
設(shè)置默認(rèn)值
迄今為止,,您已經(jīng)有了一個很好的起點,,但是要做得完美,還有很長的一段路要走,。您可能已經(jīng)想到,,下一步就要為注釋設(shè)置某個默認(rèn)值,。如果您希望用戶指定某些值,但是只有這些值與默認(rèn)值不同的時候才需要指定其他的值,,那么設(shè)置默認(rèn)值就是一種很好的辦法,。清單 6 用另一個定制注釋 —— 來自 清單 4 的 TODO 注釋類型的一個全功能版本,示范了這個概念及其實現(xiàn): 清單 6. 帶有默認(rèn)值的注釋類型
package com.oreilly.tiger.ch06;
public @interface GroupTODO {
public enum Severity { CRITICAL, IMPORTANT, TRIVIAL, DOCUMENTATION };
Severity severity()
default Severity.IMPORTANT;
String item();
String assignedTo();
String dateAssigned();
}
|
清單 6 中的 GroupTODO 注釋類型中添加了幾個新的變量,。因為該注釋類型的成員變量不是一個,,所以將一個變量命名為 value 沒有任何意義。只要成員變量多于一個,,就應(yīng)該盡可能準(zhǔn)確地為其命名,。因為不可能從 清單 5所示的簡寫形式中獲益,所以您需要創(chuàng)建雖然稍微有點冗長,,但是更容易理解的注釋類型,。
清單 6 中出現(xiàn)的另一個新特性是注釋類型定義了自己的枚舉(枚舉,即 enumeration,,通常也稱為 enums,,是 Java 5 的另一個新特性。它并沒有多么地不同凡響,,對注釋類型更是如此),。然后,清單 6 使用新定義的枚舉作為一個成員變量的類型,。
最后,,再回到我們的主題 —— 默認(rèn)值,。建立默認(rèn)值的過程非?,嵥椋枰诔蓡T聲明的后面添加關(guān)鍵字 default ,,然后提供默認(rèn)值,。正如您所料,默認(rèn)值的類型必須與成員變量聲明的類型完全相同,。同樣,,這也不是什么火箭科學(xué),只不過是詞法上的變異,。清單 7 給出了一個具體應(yīng)用中的 GroupTODO 注釋,,其中 沒有 指定 severity 成員:
清單 7. 使用默認(rèn)值
@com.oreilly.tiger.ch06.InProgress
@GroupTODO(
item="Figure out the amount of interest per month",
assignedTo="Brett McLaughlin",
dateAssigned="08/04/2004"
)
public void calculateInterest(float amount, float rate) {
// Need to finish this method later
}
|
清單 8 中使用了同一個注釋,但這一次給出了 severity 的值:
清單 8. 改寫默認(rèn)值
@com.oreilly.tiger.ch06.InProgress
@GroupTODO(
severity=GroupTODO.Severity.DOCUMENTATION,
item="Need to explain how this rather unusual method works",
assignedTo="Jon Stevens",
dateAssigned="07/30/2004"
)
public void reallyConfusingMethod(int codePoint) {
// Really weird code implementation
}
|
對注釋的注釋
結(jié)束關(guān)于注釋的討論之前(至少在本系列文章中),,我想簡要地討論一下注釋的注釋,。第 1 部分中所接觸的預(yù)定義注釋類型都有預(yù)定義的目的。但是在編寫自己的注釋類型時,,注釋類型的目的并不總是顯而易見的,。除了基本的文檔外,可能還要針對某個特定的成員類型或者一組成員類型編寫類型。這就要求您為注釋類型提供某種元數(shù)據(jù),,以便編譯器保證按照預(yù)期的目的使用注釋,。
當(dāng)然,首先想到的就是 Java 語言選擇的元數(shù)據(jù)形式 —— 注釋,。您可以使用 4 種預(yù)定義的注釋類型(稱為 元注釋)對您的注釋進(jìn)行注釋,。我將對這 4 種類型分別進(jìn)行介紹。
指定目標(biāo)
最明顯的元注釋就是允許何種程序元素具有定義的注釋類型,。毫不奇怪,,這種元注釋被稱為 Target 。但是在了解如何使用 Target 之前,,您還需要認(rèn)識另一個類,,該類被稱為 ElementType ,它實際上是一個枚舉,。這個枚舉定義了注釋類型可應(yīng)用的不同程序元素,。清單 9 給出了完整的 ElementType 枚舉: 清單 9. 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
}
|
清單 9 中的枚舉值意義很明確,您自己可以分析其應(yīng)用的目標(biāo)(通過后面的注解),。使用 Target 元注釋時,,至少要提供這些枚舉值中的一個并指出注釋的注釋可以應(yīng)用的程序元素。清單 10 說明了 Target 的用法:
清單 10. 使用 Target 元注釋
package com.oreilly.tiger.ch06;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
/**
* Annotation type to indicate a task still needs to be completed
*/
@Target({ElementType.TYPE,
ElementType.METHOD,
ElementType.CONSTRUCTOR,
ElementType.ANNOTATION_TYPE})
public @interface TODO {
String value();
}
|
現(xiàn)在,,Java 編譯器將把 TODO 應(yīng)用于類型,、方法、構(gòu)造函數(shù)和其他注釋類型,。這樣有助于避免他人誤用您的注釋類型(或者最好的地方是,, 您自己也不會因為疲憊而誤用它)。
設(shè)置保持性
|