Java 注解: 元注解, 元素类型与保留策略
注解是一种元数据形式, 为有关程序提供数据, 但这些数据并不属于程序本身. 注解对其注解代码的操作不产生直接影响.
注解有许多用途, 其中包括:
- 处理编译器信息: 编译器可以使用注解来检测错误或抑制警告
- 编译和部署时处理: 程序工具可以处理注解信息以生成代码、XML 文件等
- 运行时处理: 一些注解可以在运行时检查
下面我们将介绍, 注解的基本使用, 如何声明一个注解, 元注解, 常见注解以及可重复的注解
1. 注解的使用
常见的注解使用如下:
@Annotation(value = "xxx", num = 3)
class Demo {
}
这是个以 @ 符号开头的注解, 用于注解 Demo 类元素, 所有被注解所注解的代码都称之为元素, 包括但不限于, 参数, 类, 方法. 在示例中, class Demo
就是被注解的元素, 在注解后的括号里的 value = "xxx"
是指注解中的元素成员值(注意, 这里的元素不同于上面所说的元素, 这里的元素成员类似于类中的属性, 是注解内的成员), 而代码中的 num
就是另一个元素成员的名称, 类型是一个整型.
其实我们可能不经意间就已经使用过注解了, 比如 @Override
, 该注解实现了对所覆盖的方法的编译性约束, 即如果子类中所覆盖的方法, 如果声明方式与超类中不一致, 则会发出一个错误报告:
The method xxxMethod() of type XXXClass must override or implement a supertype method
2. 注解的声明
解下下来, 我们将简单的声明一个注解
@Documented @Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) public @interface Book { String value() default ""; String author() default ""; float price() default 0.0f; }
在上面的代码中, 我们声明了一个注解 Book
, 这里我们应该也注意到了, 注解是一种类接口的实现, 但是他使用的 @interface 声名. 同时, 我们还注意到了我们声明的注解, 也被注解了, 这种定义在 Java 中的注解我们称之为元注解 (meta-annotation), 他规定了注解的各种行为模式, 比如 @Documented
指明是否在文档中显示, @Target
指明了注解的作用对象(即元素)的类型, @Retention
指明了注解的保留时效, 这里指明注解只保留于源码中, 不保留在 .class
类文件中, 更不会进入VM的运行时.这里我们同样需要注意的是, 注解的元素成员的声明是带括号的, 比如 value()
, author()
, 元素成员是要指定类型的, 将元素类型声明为数组型也是可以的, 同时能够通过 default
关键词为元素成员提供默认值.
结合注解的基本使用, 我们可以以如下的方式使用上例中声明的注解
@Book(value = "傲慢与偏见", author = "简·奥斯汀", price = 19.9f) public static class ReadNote { }
在上面的使用使用中, 我们提供了 @Book
注解的所有元素成员的值: value, author, price
或
//相当于 @Book(value = "朝花夕拾") @Book("朝花夕拾") public static class ReadNote2 { }
在这里, 因为所有元素成员都默认值, 所以我们直接使用 "朝花夕拾"
, 为 value()
成员提供了值. value()
成员是一种特殊的注解元素成员.在只需要提供 value()
元素成员值的情况下, 是可以不提供元素成员名.
3. 元注解
在上面我们初步接触到了什么是元注解, 即 Java 中预定义的用于注解后约束所注解的注解的注解. 拆开来说就是元注解本身也是一种注解, 它注解的元素类型是注解, 用以约束所注解的注解的行为. 元注解的详细列表如下:
名称 | 说明 |
---|---|
Documented |
如果注解 |
Inherited |
指明一个注解类型是否是能被元素的子元素自动继承的 |
Native |
指明一个定义了常量值的字段也许能被本地代码所引用. |
Repeatable |
注解类型 |
Retention |
指明被 |
Target |
指明了注解类型所适用的上下文, 即声明的注解所能注解的元素类型. |
其中 @Target
的值是 ElementType
枚举的任意个成员, 如果没有为注解指定 @Target
注解, 则表明注解可以适用于上下文, ElementType
枚举的成员列表如下:
- ANNOTATION_TYPE 枚举
ElementType
的元素, 适用于注解类型的声明 - CONSTRUCTOR 枚举
ElementType
的元素, 适用于构造方法的声明 - FIELD 枚举
ElementType
的元素, 适用于注解字段的声明, 包括 enum 常量. - LOCAL_VARIABLE 枚举
ElementType
的元素, 适用于注解本地变量的声明 - METHOD 枚举
ElementType
的元素, 适用于注解方法的声明 - MODULE 枚举
ElementType
的元素, 适用于注解模块的声明 - PACKAGE 枚举
ElementType
的元素, 适用于注解包的声明 - PARAMETER 枚举
ElementType
的元素, 适用于形式参数的声明 - TYPE 枚举
ElementType
的元素, 适用于类, 接口(包括注解), 枚举的声明 - TYPE_PARAMETER 枚举
ElementType
的元素, 适用于类型参数的声明 - TYPE_USE 枚举
ElementType
的元素, 用在一个类的各个方面
而 @Retetion
的值是 RetetionPolicy
枚举的单个成员, 如果没有为注解指定 @Retetion
注解, 则默认表明注解采用 RetentionPolicy.CLASS
策略, RetetionPolicy
枚值的成员列表如下:
- CLASS 注解将由编译器记录在 .class 文件中, 但不需要在运行时由 VM 保留.
- RUNTIME 注解将由编译器记录在 .class 文件中, 并在运行时由 VM 保留, 因此可以通过反射读取它们.
- SOURCE 注解将被编译器丢弃, 只保留在源码中.
4. Java中预定义的注解
除了元注解这种预定义的注解之外, 还有一些能够帮我们约束代码编写, 更多的Java中预定义的注解, 列表如下:
名称 | 说明 |
---|---|
Deprecated |
带有 |
FunctionalInterface |
|
Override |
指明方法声明覆盖了超类中的方法声明. |
SafeVarargs |
使用 |
SuppressWarnings |
|
5. 可重复注解
一个注解能否重复注解同一个元素呢? 结果是显然的, 上面的元注解列表中已经提到了 @Repeatable
注解. 那么如何实现呢?
首先我们需要声明一个可重复注解 R
的容器注解 C
(也就是存放注解的注解);
然后再用以 C
类型为元素成员值的 @Repeatable
注解来注解可重复注解 R
, 来表明 R
是可重复使用的注解.
最后这个注解 R
就可以重复注解到要注解的元素上的了.
示例如下:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) private @interface Container { Element[] value(); } @Repeatable(Container.class) private @interface Element { String value() default ""; } @Element("准备") @Element("发令") @Element("跑") @Element("冲线") private class Running { }
在实例中, 我们首先声明了可重复注解 Element
的的容器注解 Container
, 然后使用以 Container.class
为元素成员值 @Repeatable
注解来注解可重复的注解 Element
, 然后就可以用 Element
注解重复的注解到 Running
类上了.