Java 注解
本小節(jié)我們將學(xué)習(xí) Java5
引入的一種機制 —— 注解(Annotation)。通過本小節(jié)的學(xué)習(xí),你將了解什么是注解,注解的作用,Java 中內(nèi)置注解有哪些以及注解的分類,如何自定義注解,如何處理注解等內(nèi)容。
1. 什么是注解
Java 注解(Annotation)又稱為 Java 標(biāo)注,是
Java5
開始支持加入源代碼的特殊語法元數(shù)據(jù)。Java 語言中的類、方法、變量、參數(shù)和包等都可以被標(biāo)注。Java 標(biāo)注可以通過反射獲取標(biāo)注的內(nèi)容。在編譯器生成class
文件時,標(biāo)注可以被嵌入到字節(jié)碼中。Java 虛擬機可以保留標(biāo)注內(nèi)容,在運行時可以獲取到標(biāo)注內(nèi)容。
注解是一種用于做標(biāo)注的“元數(shù)據(jù)”,什么意思呢?你可以將注解理解為一個標(biāo)簽,這個標(biāo)簽可以標(biāo)記類、方法、變量、參數(shù)和包。
回想我們學(xué)習(xí)繼承時,子類若重寫了父類的方法,可以在子類重寫的方法上使用@Override
注解:
將@Override
注解標(biāo)注在子類重寫的方法上,可檢查該方法是否正確地重寫了父類的方法,如有錯誤將會編譯報錯。
2. 注解的作用
2.1 內(nèi)置的注解
我們先看一下 Java 提供了哪些內(nèi)置注解,以及這些注解的作用。(大致了解即可)
Java 定義了一套注解,共有 10 個,5 個在 java.lang
包中,剩下 5 個在 java.lang.annotation
包中。
2.1.1 用在代碼的注解
-
@Override
:檢查該方法是否正確地重寫了父類的方法。如果重寫錯誤,會報編譯錯誤; -
@Deprecated
:標(biāo)記過時方法。如果使用該方法,會報編譯警告; -
@SuppressWarnings
:指示編譯器去忽略注解中聲明的警告; -
@SafeVarargs
:Java 7 開始支持,忽略任何使用參數(shù)為泛型變量的方法或構(gòu)造函數(shù)調(diào)用產(chǎn)生的警告; -
@FunctionalInterface
:Java 8 開始支持,標(biāo)識一個匿名函數(shù)或函數(shù)式接口。
2.1.2 用在其他注解的注解
此類注解也稱為元注解(meta annotation),在下面學(xué)習(xí)定義注解的時候,我們將會詳細(xì)講解。
-
@Retention
:標(biāo)識這個注解怎么保存,是只在代碼中,還是編入class
文件中,或者是在運行時可以通過反射訪問; -
@Documented
:標(biāo)記這些注解是否包含在用戶文檔中; -
@Target
:標(biāo)記這個注解應(yīng)該是哪種 Java 成員; -
@Inherited
:標(biāo)記這個注解是繼承于哪個注解類; -
@Repeatable
:Java 8 開始支持,標(biāo)識某注解可以在同一個聲明上使用多次。
2.2 分類
Java 注解可以分為 3 類:
-
由編譯器使用的注解:如
@Override
、@Deprecated
、@SupressWarnings
等; -
由工具處理
.class
文件使用的注解:比如有些工具會在加載class
的時候,對class
做動態(tài)修改,實現(xiàn)一些特殊的功能。這類注解會被編譯進入.class
文件,但加載結(jié)束后并不會存在于內(nèi)存中。這類注解只被一些底層庫使用,一般我們不必自己處理; -
在程序運行期間能夠讀取的注解:它們在加載后一直存在于
JVM
中,這也是最常用的注解。
3. 定義注解
學(xué)會使用注解非常簡單,很多框架都會提供豐富的注解文檔(例如 Spring)。但關(guān)鍵的一點在于定義注解,知道如何定義注解,才能看懂別人定義的注解。
下面我們來定義一個注解。
想要定義一個注解,通??煞譃?3 步:
-
創(chuàng)建注解;
-
定義注解的參數(shù)和默認(rèn)值;
-
用元注解配置注解。
關(guān)于這 3 個步驟是什么意思,如何來做,我們下面將來詳細(xì)講解。
3.1 創(chuàng)建注解
注解通過@interface
關(guān)鍵字來定義。例如,我們想要定義一個可用于檢查字符串長度的注解,實例如下:
public @interface Length {
}
Tips:通過
@interface
關(guān)鍵字定義注解,通過關(guān)鍵字interface
定義接口。注意兩者不要混淆。
在IDEA
中,我們可以在新建 Java 類的時候,選擇新建注解:
輸入我們要定義的注解的名稱(遵循類命名規(guī)范),即可創(chuàng)建一個注解:
3.2 定義參數(shù)和默認(rèn)值
注解創(chuàng)建完成后,可以向注解添加一些要接收的參數(shù),下面我們?yōu)?code>@Length注解添加 3 個參數(shù):
public @interface Length {
int min() default 0;
int max() default Integer.MAX_VALUE;
String message() default "長度不合法";
}
注解的參數(shù)類似無參數(shù)方法。另外參數(shù)的類型可以是基本數(shù)據(jù)類型、String
類型、枚舉類型、Class
類型、Annotation
類型以及這些類型的數(shù)組。
如果注解中只有一個參數(shù),或者這個參數(shù)是最常用的參數(shù),那么應(yīng)將此參數(shù)命名為value
。在調(diào)用注解時,如果參數(shù)名稱是value
,且只有一個參數(shù),那么可以省略參數(shù)名稱。(由于此注解沒有最常用特征的參數(shù),沒有使用value
)
可以使用default
關(guān)鍵字來指定參數(shù)的默認(rèn)值,推薦為每個參數(shù)都設(shè)定一個默認(rèn)值。
3.3 用元注解配置注解
在前面學(xué)習(xí) Java 內(nèi)置的注解的時候,我們已經(jīng)了解了元注解,元注解就是用于修飾其他注解的注解。
通常只需使用這些內(nèi)置元注解,就可以基本滿足我們自定義注解的需求。下面我們將會詳解 Java 內(nèi)置的 5 個元注解,你將會了解為什么需要這些元注解。
3.3.1 @Retention
Retention
譯為保留。@Retention
注解定義了一個注解的生命周期(我們前面對于 Java 注解的分類,就是通過其生命周期來劃定界限的)。它可以有如下幾種取值:
-
RetentionPolicy.SOURCE
:注解只在源碼階段保留,在編譯器進行編譯時它將被丟棄忽視; -
RetentionPolicy.CLASS
:注解只被保留到編譯進行的時候,它并不會被加載到 JVM 中; -
RetentionPolicy.RUNTIME
:注解可以保留到程序運行的時候,它會被加載進入到 JVM 中,所以在程序運行時可以獲取到它們。
下面我們使用@Retention
注解來指定我們自定義的注解@Length
的生命周期,實例如下:
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Length {
int min() default 0;
int max() default Integer.MAX_VALUE;
String message() default "長度不合法";
}
上面的代碼中,我們指定 @Length 注解可以在程序運行期間被獲取到。
3.3.2 @Documented
這個元注解的作用很簡單,標(biāo)注了此注解的注解,能夠?qū)⒆⒔庵械脑匕?Javadoc 中去。因此不做過多解釋。
3.3.3 @Target
@Target
注解是最為常用的元注解,我們知道注解可以被應(yīng)用于類、方法、變量、參數(shù)和包等處,@Target
注解可以指定注解能夠被應(yīng)用于源碼中的哪些位置,它可以有如下幾種取值:
-
ElementType.ANNOTATION_TYPE
:可以給一個注解進行注解; -
ElementType.CONSTRUCTOR
:可以給構(gòu)造方法進行注解; -
ElementType.FIELD
:可以給屬性進行注解; -
ElementType.LOCAL_VARIABLE
:可以給局部變量進行注解; -
ElementType.METHOD
:可以給方法進行注解; -
ElementType.PACKAGE
:可以給一個包進行注解; -
ElementType.PARAMETER
:可以給一個方法內(nèi)的參數(shù)進行注解; -
ElementType.TYPE
:可以給一個類型進行注解,比如類、接口、枚舉。
例如,我們定義注解@Length
只能用在類的屬性上,可以添加一個@Target(ElementType.FIELD)
:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Length {
int min() default 0;
int max() default Integer.MAX_VALUE;
String message() default "長度不合法";
}
@Target
注解的參數(shù)也可以接收一個數(shù)組。例如,定義注解@Length
可以用在屬性或局部變量上:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.LOCAL_VARIABLE})
public @interface Length {
int min() default 0;
int max() default Integer.MAX_VALUE;
String message() default "長度不合法";
}
至此,我們就完成了 @Length
注解的定義。下面,我們再來看下剩余的兩個元注解。
3.3.4 @Inherited
使用@Inherited
定義子類是否可繼承父類定義的注解。@Inherited
僅針對@Target(ElementType.TYPE)
類型的注解有效,并且僅針對類的繼承有效,對接口的繼承無效:
@Inherited
@Target(ElementType.TYPE)
public @interface TestAnnotation {
String value() default "test";
}
在使用的時候,如果一個類用到了@TestAnnotation
:
@TestAnnotation("測試注解")
public class Pet {
}
則它的子類默認(rèn)也定義了該注解:
public class Cat extends Pet {
}
3.3.5 @Repeatable
使用@Repeatable
這個元注解可以定義注解是否可重復(fù)。
例如,一個注解用于標(biāo)注一個人的角色,他可以是學(xué)生,也可以是生活委員。
@Target(ElementType.TYPE)
@Repeatable(Roles.class)
public @interface Role {
String value() default "";
}
@Target(ElementType.TYPE)
public @interface Roles {
Role[] value();
}
@Repeatable
元注解標(biāo)注了@Role
。而 @Repeatable
后面括號中的類相當(dāng)于一個容器注解,按照規(guī)定,它里面必須要有一個 value 的屬性,屬性類型是一個被 @Repeatable 注解過的注解數(shù)組。
經(jīng)過@Repeatable
修飾后,在某個類型聲明處,就可以添加多個@Role
注解:
@Role("學(xué)生")
@Role("生活委員")
public class Student {
}
4. 處理注解
4.1 嘗試使用注解
我們已經(jīng)完成了@Length
注解的定義:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.LOCAL_VARIABLE})
public @interface Length {
int min() default 0;
int max() default Integer.MAX_VALUE;
String message() default "長度不合法";
}
現(xiàn)在就可以在字段上標(biāo)注這個注解了:
public class Student {
// 標(biāo)注注解
@Length(min = 2, max = 5, message = "昵稱的長度必須在2~5之間")
private String nickname;
public Student(String nickname) {
this.setNickname(nickname);
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public static void main(String[] args) {
// 實例化對象
Student student = new Student("我的名字很長很長");
System.out.println(student.getNickname());
}
}
上面代碼中,我們在Student
類中的nickname
字段上標(biāo)注了@Length
注解,限定了其長度。
那么現(xiàn)在,是不是將其標(biāo)注在字段上面就可以自動檢查字段的長度了呢?答案是否定的。
運行過程如下,昵稱長度不合法,但并沒有拋出任何異常:

Java 的注解本身對代碼邏輯沒有任何影響,它只是一個標(biāo)注。想要檢查字段的長度,就要讀取注解,處理注解的參數(shù),可以使用反射機制來處理注解。
4.2 通過反射讀取注解
我們先來學(xué)習(xí)一下通過反射讀取注解內(nèi)容相關(guān)的 API
。
通過反射,判斷某個注解是否存在于Class
、Field
、Method
或Constructor
:
-
Class.isAnnotationPresent(Class)
-
Field.isAnnotationPresent(Class)
-
Method.isAnnotationPresent(Class)
-
Constructor.isAnnotationPresent(Class)
isAnnotationPresent()
方法的返回值是布爾類型,例如,判斷Student
類中的nickname
字段上,是否存在@Length
注解:
boolean isLengthPresent = Student.class.getDeclaredField("nickname").isAnnotationPresent(Length.class);
通過反射,獲取 Annotation 對象:
使用反射 API
讀取Annotation:
-
Class.getAnnotation(Class)
-
Field.getAnnotation(Class)
-
Method.getAnnotation(Class)
-
Constructor.getAnnotation(Class)
例如,獲取nickname
字段標(biāo)注的@Length
注解:
Length annotation = Student.class.getDeclaredField("nickname").getAnnotation(Length.class);
通過反射讀取注解的完整實例如下:
public class Student {
// 標(biāo)注注解
@Length(min = 2, max = 5, message = "昵稱的長度必須在2~6之間")
private String nickname;
public Student(String nickname) {
this.setNickname(nickname);
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public static void main(String[] args) throws NoSuchFieldException {
boolean isLengthPresent = Student.class.getDeclaredField("nickname").isAnnotationPresent(Length.class);
if (isLengthPresent) {
Length annotation = Student.class.getDeclaredField("nickname").getAnnotation(Length.class);
// 獲取注解的參數(shù)值
int min = annotation.min();
int max = annotation.max();
String message = annotation.message();
// 打印參數(shù)值
System.out.println("min=" + min);
System.out.println("max=" + max);
System.out.println("message=" + message);
} else {
System.out.println("沒有在nickname字段上找到@Length注解");
}
}
}
運行結(jié)果:
min=2
max=5
message=昵稱的長度必須在2~6之間
運行過程如下:

4.3 編寫校驗方法
獲取到了注解以及其內(nèi)容,我們就可以編寫一個校驗方法,來校驗字段長度是否合法了。我們在Student
類中新增一個checkFieldLength()
方法,用于檢查字段長度是否合法,如果不合法則拋出異常。
完整實例如下:
import java.lang.reflect.Field;
public class Student {
// 標(biāo)注注解
@Length(min = 2, max = 5, message = "昵稱的長度必須在2~5之間")
private String nickname;
public Student(String nickname) {
this.setNickname(nickname);
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public void checkFieldLength(Student student) throws IllegalAccessException {
// 遍歷所有Field
for (Field field: student.getClass().getDeclaredFields()) {
// 獲取注解
Length annotation = field.getAnnotation(Length.class);
if (annotation != null) {
// 獲取字段
Object o = field.get(student);
if (o instanceof String) {
String stringField = (String) o;
if (stringField.length() < annotation.min() || stringField.length() > annotation.max()) {
throw new IllegalArgumentException(field.getName() + ":" + annotation.message());
}
}
}
}
}
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Student student = new Student("小");
student.checkFieldLength(student);
}
}
運行結(jié)果:
Exception in thread "main" java.lang.IllegalArgumentException: nickname昵稱的長度必須在2~5之間
at Student.checkFieldLength(Student.java:32)
at Student.main(Student.java:41)
運行過程如下:

5. 小結(jié)
通過本小節(jié)的學(xué)習(xí),我們知道了注解是 Java 語言的一種標(biāo)注,Java 內(nèi)置 10 個注解,要大致了解每個注解的作用。使用@interface
關(guān)鍵字自定義注解;為注解定義參數(shù)的時候要注意其參數(shù)的類型,推薦為每個參數(shù)都設(shè)置默認(rèn)值;想要自定義注解,必須了解 Java 中內(nèi)置的 5 個元注解如何使用。
我們自定義的注解通常是用于運行時讀取的,因此必須使用@Retention(RetentionPolicy.RUNTIME)
進行標(biāo)注。本小節(jié)的概念較多且較為抽象,建議讀者親手去編寫幾個注解,再來閱讀本節(jié)內(nèi)容會有更好的理解。