註解
註解(一種後設資料形式)提供有關不屬於程式本身的程式的資料,註解對它們註解的程式碼的操作沒有直接影響。
註解有許多用途,其中包括:
- 編譯器的資訊 — 編譯器可以使用註解來檢測錯誤或抑制警告。
- 編譯時和部署時處理 — 軟體工具可以處理註解資訊以生成程式碼、XML檔案等。
- 執行時處理 — 可以在執行時檢查某些註解。
本課程介紹了可以使用註解的位置,以及如何應用註解,Java平臺標準版(Java SE API)中提供了哪些預定義註解型別,型別註解如何與可插拔型別系統結合使用來編寫具有更強型別檢查的程式碼,以及如何實現重複註解。
註解基礎知識
註解的格式
在最簡單的形式中,註解如下所示:
@Entity
符號字元(@
)向編譯器指示後面的內容是註解,在以下示例中,註解的名稱為Override
:
@Override
void mySuperMethod() { ... }
註解可以包含元素,這些元素可以是命名的,也可以是未命名的,這些元素的值如下:
@Author(
name = "Benjamin Franklin",
date = "3/27/2003"
)
class MyClass() { ... }
或:
@SuppressWarnings(value = "unchecked")
void myMethod() { ... }
如果只有一個名為value
的元素,則可以省略該名稱,如:
@SuppressWarnings("unchecked")
void myMethod() { ... }
如果註解沒有元素,則可以省略括號,如前面的@Override
示例所示。
也可以在同一宣告上使用多個註解:
@Author(name = "Jane Doe")
@EBook
class 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;
-
implements
子句:class UnmodifiableList<T> 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
}
注意:要使
@ClassPreamble
中的資訊出現在Javadoc
生成的文件中,必須使用@Documented
註解來註解@ClassPreamble
定義:
// import this to use @Documented
import java.lang.annotation.*;
@Documented
@interface ClassPreamble {
// Annotation element definitions
}
預定義的註解型別
Java SE API中預定義了一組註解型別,某些註解型別用於Java編譯器使用,有些註解型別應用於其他註解。
Java語言使用的註解型別
java.lang
中定義的預定義註解型別是@Deprecated
、@Override
和@SuppressWarnings
。
@Deprecated:@Deprecated註解表示標記已棄用和不應該再使用的元素,只要程式使用帶有@Deprecated
註解的方法、類或欄位,編譯器就會生成警告。當棄用元素時,也應使用Javadoc @deprecated
標記對其進行記錄,如以下示例所示。在Javadoc註釋和註解中使用at符號(@
)並非巧合:它們在概念上是相關的,另請注意,Javadoc標記以小寫d
開頭,註解以大寫D
開頭。
// Javadoc comment follows
/**
* @deprecated
* explanation of why it was deprecated
*/
@Deprecated
static 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
// - suppressed
objectOne.deprecatedMethod();
}
每個編譯器警告都屬於一個類別,Java語言規範列出了兩個類別:deprecation
和unchecked
,當在泛型出現之前編寫的遺留程式碼介面時,可能會發生unchecked
警告,要禁止多種類別的警告,請使用以下語法:
@SuppressWarnings({"unchecked", "deprecation"})
@SafeVarargs:@SafeVarargs註解在應用於方法或建構函式時斷言程式碼不對其varargs
引數執行可能不安全的操作,使用此註解型別時,與varargs
使用相關的未經檢查的警告被抑制。
@FunctionalInterface:Java SE 8中引入的@FunctionalInterface註解,表示型別宣告旨在成為Java語言規範定義的功能介面。
應用於其他註解的註解
應用於其他註解的註解稱為元註解,java.lang.annotation
中定義了幾種元註解型別。
@Retention:@Retention註解指定標記的註解的儲存方式:
-
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.PARAMETER
可以應用於方法的引數。 -
ElementType.TYPE
可以應用於類的任何元素。
@Inherited:@Inherited註解表明註解型別可以從超類繼承(預設情況下不是這樣),當使用者查詢註解型別並且該類沒有此型別的註解時,將查詢類的超類以獲取註解型別,此註解僅適用於類宣告。
@Repeatable:Java SE 8中引入的@Repeatable註解表明標記的註解可以多次應用於相同的宣告或型別使用,有關更多資訊,請參閱重複註解。
型別註解和可插拔型別系統
在Java SE 8發行版之前,註解只能應用於宣告,從Java SE 8發行版開始,註解也可以應用於任何型別的使用,這意味著可以在任何使用型別的地方使用註解。使用型別的一些示例包括類例項建立表示式(new
)、型別轉換、implements
子句和throws
子句,這種註解形式稱為型別註解,註解基礎知識中提供了幾個示例。
建立型別註解是為了支援改進的Java程式分析,以確保更強的型別檢查,Java SE 8版本不提供型別檢查框架,但它允許你編寫(或下載)型別檢查框架,該框架實現為與Java編譯器結合使用的一個或多個可插拔模組。
例如,你希望確保程式中的特定變數永遠不會分配給null
,你想避免觸發NullPointerException
,你可以編寫自定義外掛來檢查此問題,然後,你將修改程式碼以註解該特定變數,表明它永遠不會被賦值為null
,變數宣告可能如下所示:
@NonNull String str;
當你編譯程式碼(包括命令列中的NonNull
模組)時,編譯器會在檢測到潛在問題時輸出警告,允許你修改程式碼以避免錯誤,在更正程式碼以移除所有警告後,程式執行時不會發生此特定錯誤。
你可以使用多個型別檢查模組,其中每個模組檢查不同型別的錯誤,通過這種方式,你可以在Java型別系統的基礎上構建,在你希望的時間和位置新增特定的檢查。
通過明智地使用型別註解和可插拔型別檢查器,你可以編寫更強大且更不容易出錯的程式碼。
在許多情況下,你不必編寫自己的型別檢查模組,有第三方為你完成了這項工作,例如,你可能希望利用華盛頓大學建立的Checker Framework,該框架包括NonNull
模組、正規表示式模組和互斥鎖模組,有關更多資訊,請參閱Checker Framework。
重複註解
在某些情況下,你希望將相同的註解應用於宣告或型別用途,從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
可重複註解型別:
@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();
}
檢索註解
Reflection API中有幾種可用於檢索註解的方法,返回單個註解的方法(例如AnnotatedElement.getAnnotation(Class<T>))的行為未更改,因為如果存在所請求型別的一個註解,它們僅返回單個註解,如果存在多個所請求型別的註解,則可以通過首先獲取其容器註解來獲取它們,通過這種方式,遺留程式碼繼續工作。Java SE 8中引入了其他方法,它們掃描容器註解以一次返回多個註解,例如AnnotatedElement.getAnnotationsByType(Class<T>),有關所有可用方法的資訊,請參閱AnnotatedElement類規範。
設計注意事項
設計註解型別時,必須考慮該型別註解的基數,現在可以使用註解零次、一次,或者,如果註解的型別標記為@Repeatable
,則不止一次,通過使用@Target
元註解,還可以限制註解型別的使用位置。例如,你可以建立只能在方法和欄位上使用的可重複註解型別,仔細設計註解型別非常重要,以確保使用註解的程式設計師發現它儘可能靈活和強大。