注解
注解,元數據格式,提供關於程序的數據,這些數據不是程序的一部分。注解對它們注解的代碼的操作不產生直接影響。
注解有多種用途,包括:
- 向編譯器提供信息 —— 編譯器使用注解檢查錯誤或忽略警告
- 編譯時和部署時處理 —— 軟件工具可以處理注解信息來生成代碼,XML文件等等
- 運行時處理 —— 一些注解在運行時進行審查
本課程解釋哪裡可以使用注解,如何使用注解,以及Java平台有哪些預定義注解類型。
簡單格式,注解看起來是這樣的:
@Entity
at符(@)指示編譯器接下來是一個注解。下面的例子,注解的名字是Override:
@Override
vode mySuperMethod() {...}
注解可以包含元素,元素可以命名或不命名,元素可賦值:
@Author(
name = "Benjamin Franklin",
date = "3/27/2003"
)
class MyClass() {...}
或者
@SuppressWarnings(value = "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;
- 實現接口:
class UnmodifiableList
- 拋出異常聲明:
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.lang預定義的注解類型包括@Deprecated,,@Override,和@SuppressWarnings。
@Dreprecated @Dreprecated注解表示標記的元素已經棄用,將來不再使用。當程序使用帶@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語言規范分成兩類:棄用和未選中。泛型之前包含舊代碼的接口將產生未選中警告。為了忽略多種類型的警告,使用以下語法:
@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模塊,還有正則表達式模塊,和互斥鎖模塊。更多信息,查閱檢查者框架。
有時需要對聲明或類型使用同一個注解。隨著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元注解顯示注解類型的使用。例如,創建一個只用在方法和域的可重復注解。重要的是小心設計注解類型來確保程序員使用注解時發現它的靈活和強大。