Java
注解 Annotation
:代码中的特殊标记,这些标记可以在编译、类加载、运行时读取,并执行相应的解析处理。注解本身并不影响代码以及运行结果,只有在解析这些注解后才会生效。
Annotation
很像一个接口(反编译后发现注解就是一个接口),通过 @interface
修饰来,同一个文件中只能有一个 Annotation
的定义。
元注解
元注解 Meta Annotation
,用来修饰其他 Annotation
,即表示“其他注解”的注解。
@Retention
用于指定被修饰 Annotation
能保留多长时间。包含三个 RetentionPolicy
类型的 value
成员变量:
RetentionPolicy.SOURCE
注解只保留在源代码中,编译时直接丢弃这些注解。RetentionPolicy.CLASS
编译器将注解记录在class
文件中,在运行时JVM
不再保留注解。它也是@Retention
的默认值。RetentionPolicy.RUNTIME
编译器不仅将注解记录在class
文件中,运行时JVM
也会保留注解。也就是说程序想通过反射来获取注解信息,则必须使用RUNTIME
类型。
使用 @Retention
的示例:
1 | (value = RetentionPolicy.RUNTIME) |
@Target
只能用来修饰一个 Annotation
的定义,用于指定被修饰的 Annotation
能够修饰哪些程序元素。包含如下 ElementType
类型的 value
成员变量:
ElementType.TYPE
:指定该注释可以修饰类、接口或枚举ElementType.FIELD
:指定该注解只能修饰成员变量ElementType.METHOD
:指定该注解只能修饰方法ElementType.PARAMETER
:指定该注解只能修饰参数ElementType.CONSTRUCTOR
:指定该注解只能修饰构造器ElementType.LOCAL_VARIABLE
:指定该注解只能修饰局部变量(不能通过反射解析,class
文件并不保留局部变量注解。只能通过源码在编译前解析)ElementType.ANNOTATION_TYPE
:指定该注解只能修饰注解ElementType.PACKAGE
:指定该注解只能修饰包定义ElementType.TYPE_PARAMETER
:指定该注解只能参数化类型ElementType.TYPE_USE
:指定该注解可以修饰上面几种任意类型
如果需要指定多个 ElementType
类型的成员变量,使用数组赋值。
1 | // 修饰单个元素 |
@Documented
修饰其他 Annotation
时,通过 javadoc
工具提取文档会保留这个 Annotation
信息。
1 |
|
javadoc
生成文档时,myJavaDocInfo
方法文档会保留 @Testable
信息。如果 Testable
的元注解中去掉 @Documented
,则生成的文档不会保留 @Testable
信息。
@Inherited
修饰其他 Annotation
时,表示该注解具有继承性。比如 @A
被 @Inherited
注解,@A
注解父类,则其所有子类自动被 @A
注解。
1 | // 1. Annotation |
运行结果显示,虽然 Son
并没有被 Inheritable
注解,但是自动从 Father
那继承了该注解。
@Repeatable
Java 8
加入的新特性,表示注解中相同元素的值可以取多个。比如某个类需要扮演多个角色:医生、护士、警察。在 Java 8
之前是不允许有相同注解重复的。
1 | // 1. 定义角色注解 |
元注解的源码
1 | // 1. Retention |
自定义注解
自定义注解使用 @interface
作为关键字。
定义
格式,注意成员变量后有个双括号 ()
,以 ;
结尾:
1 | 访问控制符 @interface 注解名称{ |
标记
Annotation
如果没有定义成员变量,则该注解被称为标记。这种注解仅仅利用自身存在来给我们提供信息,比如@Override
等等。1
2(ElementType.PARAMETER)
public TestPara{}元数据
Annotation
包含成员变量,他们可以接受更多的元数据,每个成员变量可以有默认值,使用default
关键字。1
2
3
4
5
6
7(RetentionPolicy.RUNTIME)
public MetaAnnotation {
String author();
String date();
int currentVersion() default 1;
String[] reviewer();
}
使用方法
格式:
1 | @自定义注解(成员变量1=指定值1, |
成员变量是以名字和值 name=value
形式成对出现的,如果只有一个,可以省略成员变量名称。被注解元素,是依据 @Target
指定的类型。
1 | ( |
Java
预置的基本注解
@Override
@Override
只能注解方法,用来表示方法重写。编译器会检查该方法,保证父类要包含一个被重写的方法,否则会编译报错。
1 | (ElementType.METHOD) |
@Deprecated
@Deprecated
用于注解方法,类,字段等,表示该程序元素已过时。当其他程序调用时,编译器会给出警告。
1 |
|
@SuppressWarnings
@SuppressWarnings
注解表示取消显示指定的编译器警告。比如:unused, unchecked, all
等警告。
1 | ({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) |
@SafeVarargs
Java 7
新增的注解,参数安全类型注解,它的目的是提醒开发者不要用参数做一些不安全的操作。比如堆污染:把一个不带泛型的对象赋给一个带泛型的变量是,就会发生堆污染。
1 |
|
@FunctionalInterface
Java 8
新增的注解,表示函数式接口注解。
函数式接口 Functional Interface
:就是只有一个方法的普通接口,使用 @FunctionalInterface
注解后,编译器会检查确保符合规范。
1 |
|
其他
注解是一种类型
注解是具体的类型,可以使用 instanceof
来判断,示例:if (annotation instanceof GET) {...}
。
注解的解析
- 通过反射处理注解
这种处理方式要求注解必须是@Retention(RetentionPolicy.RUNTIME)
类型的。在运行时,通过反射来获取并解析注解。但是反射比较慢,所以需要考虑效率问题。 - 编译时处理注释
因为反射效率和性能问题,Java
还提供了编译时解析注解并自动生成代码。Java 7
之前可以使用APT: Annotation Processing Tool
工具提取注解并解析后,生成对应的代码。但是Java 7
开始降级移除了APT
并采用JSR 269: Pluggable Annotation Processing API
,原因及说明见官网解释,原因。
参考文档
- 疯狂
Java
讲义 – 第 14 章Annotation
注释 - 官方教程 Annotation
- 秒懂 Java 注解
- Java Annotation 及几个常用开源项目注解原理简析
- 元注解之 Repeatable
- Java 8 新特性:扩展注解