注解

什么是注解

Java 中的注解类型 (Annotation Types,下称注解)可以理解成是对代码的一种特殊标记,我们可以把注解标记在类、方法、变量、参数等 Java 元素上。这个标记可以给编写代码的人看,但是它主要还是提供给程序本身进行解析。

也就是说,注解本身其实不能起到作用,还需要其他程序去主动解析注解来采取相应的动作。如果没有相应的解析代码,那么注解就起不到任何作用。

如何定义一个注解

注解

注解其实就是一种新的特殊的接口类型,为了和普通的接口区分开,在定义注解时需要在关键字 interface 前添加 @ 符号,例如:

1
2
public @interface MyAnnotation {
}

同时,我们定义的每一个注解都会自动实现 java.lang.annotation.Annotation 接口。

注解元素

我们还可以在注解中添加一些 注解类型元素 (Annotation Type Elements,下称注解元素)。只需要在注解的实现部分添加一些方法声明 (method declarations),这些方法就会被定义成注解元素。

但是这些方法声明中不能包含形式参数、类型参数、抛出异常 (throws clause),方法也不能被 defalut、static、private、protected 等修饰,只能使用 public (不写则默认为 public)。

另外,这些声明的方法的返回值类型,只能是以下几种:

  • 原始数据类型
  • 字符串
  • 枚举类型
  • 注解类型
  • 以上类型的一维数组

例如:

点击折叠/展开
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 正确的定义
enum Level {BAD, INDIFFERENT, GOOD}

class User {
}

public @interface MyAnnotation {
// 原始数据类型
int age();

// 字符串
String name();

// 字数串数组
String[] names();

// 类
Class<User> userClass();

// 枚举类型
Level level();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 错误的定义
class User {
}

public @interface MyAnnotation {
// 不能使用包装类型
Integer age();

// 不能使用自定义类型
User user();

// 不能使用二维数组
String[][] names();

// 不能包含形式参数
int number(int id);

// 不能抛出异常
String name() throws RuntimeException;

// 不能标记为静态
static int height();

// 不能标记为 default
default int id();
}

我们还可以为注解元素指定默认值,例如:

1
2
3
public @interface MyAnnotation {
int age() default 20;
}

注解的分类

根据注解中的注解元素个数,还可以将注解分为:

  • 标记注解:注解中不含有注解元素
  • 单元素注解:注解中只有一个注解元素
  • 普通注解:注解中含有超过一个注解元素

其中,我们约定单元素注解中的元素统一命名为 value,如:

1
2
3
public @interface MyAnnotation {
int value();
}

常用的注解

元注解

元注解就是用来修饰注解的注解,经常用到的有:

  • Target
  • Retention
  • Inherited
  • Document

@Target

这个元注解被用来说明某个注解可以用来修饰什么 Java 元素,声明如下:

1
2
3
4
5
6
7
8
9
10
11
12
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}

它的取值范围被定义在枚举类 ElementType 中:

点击折叠/展开
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
// 类、接口、枚举
TYPE,

/** Field declaration (includes enum constants) */
// 成员变量
FIELD,

/** Method declaration */
// 方法
METHOD,

/** Formal parameter declaration */
// 形式参数
PARAMETER,

/** Constructor declaration */
// 构造函数
CONSTRUCTOR,

/** Local variable declaration */
// 局部变量
LOCAL_VARIABLE,

/** Annotation type declaration */
// 注解
ANNOTATION_TYPE,

/** Package declaration */
// 包
PACKAGE,

/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,

/**
* Use of a type
*
* @since 1.8
*/
// Use of a type
TYPE_USE
}
TODO 最后两个我还看不懂是什么意思

在 @Target 注解本身就使用了 @Target(ElementType.ANNOTATION_TYPE),说明 @Target 注解只能用来修饰注解。

@Retention

这个注解用来说明某个注解的存活时间。

1
2
3
4
5
6
7
8
9
10
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}

它的取值范围被定义在枚举类 RetentionPolicy 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
* 注解会被编译器丢弃
*/
SOURCE,

/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
* 注解会被编译器保存到 .class 文件中,但是不会被 JVM 保留到运行期间。这是默认的取值。
*/
CLASS,

/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* 注解会被保留到运行时,可以通过反射读取到。
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}

@Inherited

这个注解用来表示某个注解在类继承的时候,父类上的注解是否可以被子类继承到。

这个注解只是用来标记是否能被继承,因此没有任何注解元素,是一个标记注解:

1
2
3
4
5
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

其他注解

@Override

该注解用来标记一个方法在重写父类(或接口)中的方法,也是一个标记注解。当编译器遇到这个注解时,会主动检查重写的条件,防止出现一些低级错误,如参数列表不符合、抛出了更宽泛的异常等。因此这个注解的存活周期只到编译时 (RetentionPolicy.SOURCE):

1
2
3
4
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

@Deprecated

这个注解用来标记某个类、方法、成员变量等是否被弃用。编译器会主动发出警告,如果一个弃用的 Java 元素被使用或者被重写在非 Deprecated 的方法中 (Compilers warn when a deprecated program element is used or overridden in non-deprecated code.)。

1
2
3
4
5
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

注解的使用

普通注解

在使用普通注解时需要指定注解中定义的所有注解元素的取值 (含有默认值的注解元素可以不指定,会使用默认值)。同时不能指定注解中没有定义的注解元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// MyAnnotation.java 定义注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String name();
int age() default 20;
String location();
}

// Main.java 使用注解
public class Main {
// 可以全部指定注解元素的值
@MyAnnotation(name="xuewenG", age = 21, location = "China")
private User userA;

// 有默认值的注解元素可以使用默认值
@MyAnnotation(name="xuewenG", location = "China")
private User userB;

// 错误。注解元素 location 没有默认值,必须被赋值
@MyAnnotation(name="xuewenG")
private User userC;

// 错误。没有被定义的注解元素 level 不能出现在列表中
@MyAnnotation(name="xuewenG", location = "China", level="LEVEL_A")
private User userD;
}

标记注解

标记注解可以被直接使用,不需要给注解元素赋值。

1
2
3
4
5
6
7
8
9
10
11
// MyAnnotation.java 定义注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}

// Main.java 使用注解
public class Main {
@MyAnnotation
private User user;
}

注意,如果一个注解中的所有注解元素都具有默认值,那么该注解也可以被当作标记注解使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// MyAnnotation.java 定义注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String name() default "xuewenG";
int age() default 20;
String location() default "China";
}

// Main.java 使用注解
public class Main {
@MyAnnotation
private User user;
}

单元素注解

单元素注解只有一个特殊的地方,即在使用时可以不指定注解元素的名称(但是在定义注解时要按照约定来定义注解元素)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// MyAnnotation.java 定义注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value();
}

// Main.java 使用注解
public class Main {
// 可以指名注解元素名称
@MyAnnotation(value = "Value")
private User userA;

// 也可以不指名注解元素名称
@MyAnnotation("Value")
private User userB;
}

以下使用方式是错误的,因为在定义单元素注解时没有按照约定将注解元素名称定义为 value

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// MyAnnotation.java 定义注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String name();
}

// Main.java 使用注解
public class Main {
// 错误。没有名称为 value 的注解元素
@MyAnnotation(value = "Value")
private User userA;

// 错误。没有名称为 value 的注解元素
@MyAnnotation("Value")
private User userB;
}

注意,如果一个注解中除了 value 注解元素之外还有其他注解,并且其他注解都含有默认值,那么这个注解也可以被当做单元素注解使用,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// MyAnnotation.java 定义注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value();
String name() default "xuewenG";
int age() default 20;
}

// Main.java 使用注解
public class Main {
// 当作单元素注解使用
@MyAnnotation(value = "Value")
private User userA;

// 当作单元素注解使用
@MyAnnotation("Value")
private User userB;

// 如果需要覆盖默认值,则必须指明所有提到的注解元素的名称
@MyAnnotation(value = "Value", name = "xuewnG")
private User userC;
}

参考资料:

The Java Language Specification, Java SE 8 Edition - Chapter 9.6. Annotation Types
JDK 1.8.0_231 Source Code