[譯]Java註解教程
註解
註解,中繼資料格式,提供關於程式的資料,這些資料不是程式的一部分。註解對它們註解的代碼的操作不產生直接影響。
註解有多種用途,包括:
- 向編譯器提供資訊 —— 編譯器使用註解檢查錯誤或忽略警告
- 編譯時間和部署時處理 —— 軟體工具可以處理註解資訊來產生代碼,XML檔案等等
- 運行時處理 —— 一些註解在運行時進行審查
本課程解釋哪裡可以使用註解,如何使用註解,以及Java平台有哪些預定義註解類型。
註解基礎註解格式
簡單格式,註解看起來是這樣的:
@Entity
at符(@)指示編譯器接下來是一個註解。下面的例子,註解的名字是Override:
@Overridevode mySuperMethod() {...}
註解可以包含元素,元素可以命名或不命名,元素可賦值:
@Author( name = "Benjamin Franklin", date = "3/27/2003")class MyClass() {...}
或者
@SuppressWarnings(value = "unchecked")void myMethod() { ... }
如果註解沒有元素,則括弧可以省略,就如前面的@Override一樣。
可給聲明使用多個註解:
@Author(name = "Jane Doe")@EBookclass MyClass { ... }
如果註解有相同類型,則這被稱為重複註解:
@Author(name = "Jane Doe")@Author(name = "John Smith")class MyClass { ... }
隨著Java SE 8的發布,重複註解得到了支援。更多資訊,詳見重複註解。
註解類型可以是Java SE API中java.lang或java.lang.annotation包中定義的類型之一。在前面的例子中,Override和SuppressWarnings是預定義Java註解。也可以自訂註解類型。前例中的Author和Ebook註解都是自訂註解類型。
註解用於何處
註解可以用在聲明:聲明類,域,方法和其他程式元素。當用於聲明時,每個註解按照慣例都單獨一行。
隨著Java SE 8的發布,註解也可以應用到類型使用。例子如下:
- 類執行個體建立運算式:
new @Interned MyObject();
- 強制轉換:
myString = (@NonNull String) str;
- 實現介面:
class UnmodifiableList implements @Readonly List<@Readonly T> { ... }
- 拋出異常聲明:
void monitorTemperature() throws @Critical TemperatureException { ... }
這種類型的註解稱為類型註解。更多詳情,請查看類型註解與可插拔類型系統。
聲明註解
很多註解替代代碼中的注釋。
假設一個軟體集團通常在每個類檔案的起始都會增加重要訊息的注釋:
public class Generation3List extends Generation2List { // Author: John Doe // Date: 3/17/2002 // Current revision: 6 // Last modified: 4/12/2004 // By: Jane Doe // Reviewers: Alice, Bill, Cindy // class code goes here}
為了使用注釋增加相同的中繼資料,首先必須定義註解類型。文法如下:
@interface ClassPreamble { String author(); String date(); int currentRevision() default 1; String lastModified() default "N/A"; String lastModifiedBy() default "N/A"; // Note use of array String[] reviewers();}
註解類型定義看起來類似介面定義,關鍵字interface前增加at符(@)(@ = AT,用於註解類型)。註解類型是一種介面,介面將在後續章節中講解。現在,你不需要理解介面。
前面的註解定義的主體包含註解類型元素聲明,這看起來很像方法。注意,可以定義可選的預設值。
註解類型定以後,可以使用這種註解,並填充一些值,例如:
@ClassPreamble ( author = "John Doe", date = "3/17/2002", currentRevision = 6, lastModified = "4/12/2004", lastModifiedBy = "Jane Doe", // Note array notation reviewers = {"Alice", "Bob", "Cindy"})public class Generation3List extends Generation2List {// class code goes here}
預定義註解類型
Java SE API預定義了一組註解類型。某些註解用於Java編譯器,一些適用於其他註解。
Java語言使用的註解類型
java.lang預定義的註解類型包括@Deprecated,,@Override,和@SuppressWarnings。
@Dreprecated @Dreprecated註解表示標記的元素已經棄用,將來不再使用。當程式使用帶@Deprecated註解的方法,類或者域時,編譯器將產生警告。當一個元素棄用時,在文檔中應當使用Javadoc的@deprecated標籤,如下例所示。Javadoc注釋和註解中使用at符(@)不是巧合:它們概念上有聯絡。另外,Javadoc中以小寫d開通,註解以大寫D開頭。
// Javadoc comment follows/** * @deprecated * explanation of why it was deprecated */@Deprecatedstatic void deprecatedMethod() { }}
@Override @Override註解通知編譯器該元素沖在了超類中聲明的元素。重寫方法將在介面與繼承中討論。
// mark method as a superclass method// that has been overridden@Override int overriddenMethod() { }
然而使用這個註解重寫方法不是必須的,它有助於防止錯誤。如果標記@Override的方法不能正確重寫超類中的方法,編譯器將產生一個錯誤。
@SuppressWarnings @SuppressWarnings註解告訴編譯器忽略指定的警告。下面的例子,使用了一個棄用的方法,編譯器通常產生一個警告。這種情況下,使用該註解將忽略這個警告。
// use a deprecated method and tell // compiler not to generate a warning@SuppressWarnings("deprecation")void useDeprecatedMethod() {// deprecation warning// - suppressedobjectOne.deprecatedMethod();}
編譯器對警告進行分類。Java語言規範分成兩類:棄用和未選中。泛型之前包含舊代碼的介面將產生未選中警告。為了忽略多種類型的警告,使用以下文法:
@SuppressWarnings({"unchecked", "deprecation"})
@SafeVarargs @SafeVarargs標註,適用於方法或建構函式,斷言不對指定代碼中的可變參數執行潛在的不安全操作。當使用這種註解類型時,可變參數相關的未選中警告將被忽略。
@FunctionalInterface @FunctionalInterface註解,在Java SE 8中引入,表示該型別宣告代表如Java語言規範所定義的函數介面。
應用於其他註解的註解
應用於其他註解的註解稱為元註解,java.lang.annotation中定義了一些元註解。
@Retention @Rentention註解指定標記註解的儲存方式:
RetentionPolicy.SOURCE —— 標記註解只在代碼級持有,由編譯器忽略。 RetentionPolicy.CLASS —— 標記註解在編譯時間由編譯器持有,由Java虛擬機器(JVM)忽略。 RetentionPolicy.RUNTIME —— 標記註解又JVM持有,可在運行環境使用。
@Documented @Documented註解表明使用指定註解的那些元素應當使用Javadoc工具文檔化。(預設,註解不包含在Javadoc中。)更多資訊,請參見Javadoc工具頁。
@Target @Target註解標記其他註解,來限制目標註解可以適用於何種Java元素。目標註解指定以下元素作為它的值:
- ElementType.ANNOTATION_TYPE 可以適用於註解類型
- ElementType.CONSTRUCTOR 可以適用於構造器
- ElementType.FIELD 可以適用於域或屬性
- ElementType.LOCAL_VARIABLE 可以適用於本地變數
- ElementType.METHOD 可以適用於方法級註解
- ElementType.PACKAGE可以適用於包註解
- ElementType.PARAMETE 可以適用於方法的參數
- ElementType.TYPE 可以適用於類的任何元素
@Inherited @Inherited註解表明註解類型可以從超類繼承。(預設為false。)當使用者查詢註解類型,該類沒有標記這種類型的註解,將會查詢父類的註解類型。這種註解只適用於類聲明。
@Repeatable @Repeatable註解,Java SE 8引入,表明標記的註解可被重複使用相同的聲明或類型。更多資訊,參見重複註解。
類型註解與可插拔類型系統
Java SE 8發布之前,註解只能用於聲明。隨著Java SE 8的發布,註解也適用於任何類型使用。這意味著,註解可以適用於任何使用類型的地方。類型可以使用的方式有類執行個體化建立運算式(new),強制轉換,介面實現和拋出異常。這種形式的註解被稱為類型註解,註解基礎提供了一些栗子。
類型註解的引入是為了支援改進確保強型別檢查的Java程式的方式分析。Java SE 8沒提供一個類型檢查架構,但是允許寫(或下載)類型檢查架構,該架構作為一個或多個可插拔模組用於與Java編譯器結合。
例如,你想確定程式中的一個特殊變數從不被置為null;你想避免出發NullPointerException。你可以寫一個自訂插入模組來檢查它。你可以修改代碼來註解這個特殊的變數,指明它永不被分配為null。變數聲明可能是這樣:
@NonNull String str;
編譯代碼時,命令列包括NonNull模組,編譯器如果檢查到潛在的問題會列印一個警告,提醒你修改代碼來避免發生錯誤。修改所有警告後,改特殊的錯誤在程式執行時將不會發生。
可使用多類型檢查模組,每個模組檢查一種類型的錯誤。這樣,可以基於Java類型系統進行構建,何時何地只要需要就可以增加指定類型的檢查。
隨著明智的使用註解以及可插拔類型檢查的存在,代碼品質提高了,錯誤減少了。
很多情況想,不需要寫類型檢查模組。第三方工具已經實現了。例如,可以使用華盛頓大學創造的檢查架構。這個架構套件括NonNull模組,還有Regex模組,和互斥鎖模組。更多資訊,查閱檢查者架構。
重複註解
有時需要對聲明或類型使用同一個註解。隨著Java SE 8的發布,重複註解可以出列這個需求。
例如,代碼使用了計時器服務使得在指定之間或確定間隔執行特定方法,類似UNIX系統的cron服務。現在你想增加一個計時器來執行一個方法,doPeriodicCleanup,在每月睡會一天和每個周五的下午11點。為了啟動計時器,建立一個@Schedule註解並兩次使用到doPeriodicCleanup方法。第一次指定每月最後一天,第二次指定周五的下午11點,如下代碼所示:
@Schedule(dayOfMonth="last")@Schedule(dayOfWeek="Fri", hour="23")public void doPeriodicCleanup() { ... }
前面的例子展示了想方法增加註解。你可以在任何標準註解使用的地方重複使用註解。例如,有一個類處理未鑒權訪問異常。註解這個類,一個@Alert用於經理,其他用於管理員:
@Alert(role="Manager")@Alert(role="Administrator")public class UnauthorizedAccessException extends SecurityException { ... }
出於相容性原因,重複註解儲存在包含註解中,包含註解由Java
編譯器產生。為了讓編譯器做到這一點,代碼中需要兩個聲明。
* 步驟1 : 聲明一個重複註解類型 *
註解類型必須標明@Repeatable元註解。下面的例子自訂了@Schedule重複註解類型:
import java.lang.annotation.Repeatable;@Repeatable(Schedules.class)public @interface Schedule { String dayOfMonth() default "first"; String dayOfWeek() default "Mon"; int hour() default 12;}
@Repeatable元註解的值,括弧內的,是包含註解的類型,Java編譯器產生用於儲存重複註解。例子中,包含註解類型是Schedules,所以重複@Schedule註解將儲存在@Schedules註解中。
使用未聲明為可重複的註解到聲明時將產生一個編譯時間錯誤。
步驟 2 : 聲明包含註解類型
包含註解類型必須指定一個類型為數組的value元素。數群組類型的組件必須是可重複註解類型。Schedules包含註解類型的聲明如下:
public @interface Schedules { Schedule[] value();}
檢索註解
在反射API中有一些方法可以用於檢索註解。這些方法的行為是返回一個單一註解,例如AnnotatedElement.getAnnotationByType(Class),如果搜尋到請求的類型,它只返回單一註解。如果請求類型的多個註解存在,可以先得到它們的包含註解。只有,舊代碼可以繼續使用。Java SE 8增加的其他方法掃麵包含註解,一次返回多個註解,例如AnnotatedElement.getAnnotations(Class)。參見AnnotatedElement類規範中所有可用方法資訊。
設計注意事項
當設計註解類型時,你必須考慮註解類型的基數。現在可以使用註解零次,一次或者多次,如果註解類型標記為@Repeatable。也可以使用@Target元註解顯示註解類型的使用。例如,建立一個只用在方法和域的可重複註解。重要的是小心設計註解類型來確保程式員使用註解時發現它的靈活和強大。