Java 注解
什么是注解
Java 中的注解类型
(Annotation Types,下称注解)可以理解成是对代码的一种特殊标记,我们可以把注解标记在类、方法、变量、参数等 Java 元素上。这个标记可以给编写代码的人看,但是它主要还是提供给程序本身进行解析。
也就是说,注解本身其实不能起到作用,还需要其他程序去主动解析注解来采取相应的动作。如果没有相应的解析代码,那么注解就起不到任何作用。
如何定义一个注解
注解
注解其实就是一种新的特殊的接口类型,为了和普通的接口区分开,在定义注解时需要在关键字 interface
前添加 @
符号,例如:
public @interface MyAnnotation {
}
同时,我们定义的每一个注解都会自动实现 java.lang.annotation.Annotation
接口。
注解元素
我们还可以在注解中添加一些 注解类型元素
(Annotation Type Elements,下称注解元素)。只需要在注解的实现部分添加一些方法声明 (method declarations),这些方法就会被定义成注解元素。
但是这些方法声明中不能包含形式参数、类型参数、抛出异常 (throws clause),方法也不能被 defalut、static、private、protected 等修饰,只能使用 public (不写则默认为 public)。
另外,这些声明的方法的返回值类型,只能是以下几种:
- 原始数据类型
- 字符串
- 类
- 枚举类型
- 注解类型
- 以上类型的一维数组
例如:
// 正确的定义
enum Level {BAD, INDIFFERENT, GOOD}
class User {
}
public @interface MyAnnotation {
// 原始数据类型
int age();
// 字符串
String name();
// 字数串数组
String[] names();
// 类
Class<User> userClass();
// 枚举类型
Level level();
}
// 错误的定义
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();
}
我们还可以为注解元素指定默认值,例如:
public @interface MyAnnotation {
int age() default 20;
}
注解的分类
根据注解中的注解元素个数,还可以将注解分为:
- 标记注解:注解中不含有注解元素
- 单元素注解:注解中只有一个注解元素
- 普通注解:注解中含有超过一个注解元素
其中,我们约定单元素注解中的元素统一命名为 value
,如:
public @interface MyAnnotation {
int value();
}
常用的注解
元注解
元注解就是用来修饰注解的注解,经常用到的有:
- Target
- Retention
- Inherited
- Document
@Target
这个元注解被用来说明某个注解可以用来修饰什么 Java 元素,声明如下:
@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
中:
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
}
在 @Target 注解本身就使用了 @Target(ElementType.ANNOTATION_TYPE)
,说明 @Target 注解只能用来修饰注解。
@Retention
这个注解用来说明某个注解的存活时间。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
它的取值范围被定义在枚举类 RetentionPolicy
中:
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
这个注解用来表示某个注解在类继承的时候,父类上的注解是否可以被子类继承到。
这个注解只是用来标记是否能被继承,因此没有任何注解元素,是一个标记注解:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
其他注解
@Override
该注解用来标记一个方法在重写父类(或接口)中的方法,也是一个标记注解。当编译器遇到这个注解时,会主动检查重写的条件,防止出现一些低级错误,如参数列表不符合、抛出了更宽泛的异常等。因此这个注解的存活周期只到编译时 (RetentionPolicy.SOURCE):
@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.)。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
注解的使用
普通注解
在使用普通注解时需要指定注解中定义的所有注解元素的取值 (含有默认值的注解元素可以不指定,会使用默认值)。同时不能指定注解中没有定义的注解元素。
// 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="xuewen", age = 21, location = "China")
private User userA;
// 有默认值的注解元素可以使用默认值
@MyAnnotation(name="xuewen", location = "China")
private User userB;
// 错误。注解元素 location 没有默认值,必须被赋值
@MyAnnotation(name="xuewen")
private User userC;
// 错误。没有被定义的注解元素 level 不能出现在列表中
@MyAnnotation(name="xuewen", location = "China", level="LEVEL_A")
private User userD;
}
标记注解
标记注解可以被直接使用,不需要给注解元素赋值。
// MyAnnotation.java 定义注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}
// Main.java 使用注解
public class Main {
@MyAnnotation
private User user;
}
注意,如果一个注解中的所有注解元素都具有默认值,那么该注解也可以被当作标记注解使用。
// MyAnnotation.java 定义注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String name() default "xuewen";
int age() default 20;
String location() default "China";
}
// Main.java 使用注解
public class Main {
@MyAnnotation
private User user;
}
单元素注解
单元素注解只有一个特殊的地方,即在使用时可以不指定注解元素的名称(但是在定义注解时要按照约定来定义注解元素)。
// 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
:
// 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 注解元素之外还有其他注解,并且其他注解都含有默认值,那么这个注解也可以被当做单元素注解使用,例如:
// MyAnnotation.java 定义注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value();
String name() default "xuewen";
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