Java 注解是 JDK5 引入的一种注释机制,或者说是一种特殊的标签。Java 中的类、方法、变量、属性和包等都可以被注解。
注解以 @ 符号开头,例如 @Override 注解。
@Override
public String toString() {
return "Java注解简介";
}
注解属于一种数据类型,注解的本质上就是一个继承了 Annotation 接口的接口。
注解有一些限制条件,Java用元注解来产生这些限制因素,元注解也是标签,这些标签是用来描述注解的原始标签。
1. 注解的作用
1) 编译期处理:
编译器可以利用注解来探测错误和警告信息,比如 @Override,一旦编译器检测到某个方法被修饰了 @Override 注解,编译器就会检查当前方法的方法签名是否真正重写了父类的某个方法,也就是比较父类中是否具有一个同样的方法签名。
检查涉及到的原理为 APT(Annotation Processing Tool) 技术。
2) 运行期处理(注解与反射):
通过反射机制获取注解,进行实际的业务代码处理。
运行期反射是虚拟机将所有生命周期在 RUNTIME 的注解取出来放到一个 map 中,并创建一个 AnnotationInvocationHandler 实例,把这个 map 传递给它。
*注:注解是给编译器、 APT(Annotation Processing Tool) 用的。
2. 内置注解
1) 元注解
元注解是负责对其它注解进行说明的注解,自定义注解时需要使用元注解。
Java 5 定义了 @Documented、@Target、@Retention 和 @Inherited。
Java 7 增加了 @SafeVarargs。
Java 8 增加了 @Repeatable、@Native 和 @FunctionalInterface。
这些注解都可以在 java.lang.annotation 包中找到。
下面主要介绍每个元注解的作用及使用:
(1) @Documented
@Documented 是一个标记注解,没有成员变量。用 @Documented 注解修饰的注解类会被 JavaDoc 工具提取成文档。默认情况下,JavaDoc 是不包括注解的,但如果声明注解时指定了 @Documented,就会被 JavaDoc 之类的工具处理,所以注解类型信息就会被包括在生成的帮助文档中。
(2) @Target
@Target 注解用来指定一个注解的使用范围,即被 @Target 修饰的注解可以用在什么地方。@Target 注解有一个成员变量(value)用来设置适用目标,value 是 java.lang.annotation.ElementType 枚举类型的数组,下表为 ElementType 常用的枚举常量。
名称 说明
CONSTRUCTOR 用于构造方法
FIELD 用于成员变量(包括枚举常量)
LOCAL_VARIABLE 用于局部变量
METHOD 用于方法
PACKAGE 用于包
PARAMETER 用于类型参数(JDK 1.8新增)
TYPE 用于类、接口(包括注解类型)或 enum 声明
(3) @Retention
@Retention 用于描述注解的生命周期,也就是该注解被保留的时间长短。@Retention 注解中的成员变量(value)用来设置保留策略,value 是 java.lang.annotation.RetentionPolicy 枚举类型,RetentionPolicy 有 3 个枚举常量,如下所示。
SOURCE:在源文件中有效(即源文件保留)
CLASS:在 class 文件中有效(即 class 保留)
RUNTIME:在运行时有效(即运行时保留)
生命周期大小排序为 SOURCE < CLASS < RUNTIME,前者能使用的地方后者一定也能使用。如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解;如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS 注解;如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,则可选用 SOURCE 注解。
(4) @Inherited
@Inherited 是一个标记注解,用来指定该注解可以被继承。使用 @Inherited 注解的 Class 类,表示这个注解可以被用于该 Class 类的子类。就是说如果某个类使用了被 @Inherited 修饰的注解,则其子类将自动具有该注解。
(5) @SafeVarargs
@SafeVarargs 忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
(6) @Repeatable
@Repeatable 注解是 Java 8 新增加的,它允许在相同的程序元素中重复注解,在需要对同一种注解多次使用时,往往需要借助 @Repeatable 注解。Java 8 版本以前,同一个程序元素前最多只能有一个相同类型的注解,如果需要在同一个元素前使用多个相同类型的注解,则必须使用注解“容器”。
(7) @Native
使用 @Native 注解修饰成员变量,则表示这个变量可以被本地代码引用,常常被代码生成工具使用。对于 @Native 注解不常使用,了解即可。
(8) @FunctionalInterface
主要用于编译级错误检查,加上该注解,当写的接口不符合函数式接口定义的时候,编译器会报错。
2) 其它内置注解
@Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
@Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
@SuppressWarnings - 指示编译器去忽略注解中声明的警告。
3. 自定义注解
使用 @interface 关键字定义注解,格式:
public @interface TestTag {
}
访问修饰符和类一样有两种:public 和默认访问权限(默认不写)。
一个源程序文件中可以声明多个注解,但只能有一个是公有访问权限的注解。且源程序文件命名和公有访问权限的注解名一致。
1) 标记注解
没有定义成员变量的注解类型被称为标记注解。这种注解仅利用自身的存在与否来提供信息,如前面介绍的 @Override、@TestTag 等都是标记注解。
2) 元数据注解
包含成员变量的注解,因为它们可以接受更多的元数据,所以也被称为元数据注解。元数据注解的成员变量也可有访问修饰符:public 和默认权限(默认不写)。
以下代码声明了一个 DemoTag 注解,定义了两个成员变量,分别是 name 和 age,使用该注解时为它的成员变量进行了赋值。
public @interface DemoTag {
// 定义带两个成员变量的注解
// 注解中的成员变量以方法的形式来定义
String name();
int age();
}
public class Test {
// 使用带成员变量的注解时,需要为成员变量赋值
@DemoTag(name="xx", age=6)
public void info() {
...
}
...
}
注解中的成员变量也可以有默认值,可使用 default 关键字。如下代码:
public @interface DemoTag {
// 定义了两个成员变量的注解
// 使用 default 为两个成员变量指定初始值
String name() default "自定义注解";
int age() default 9;
}
public class Test {
// 使用带成员变量的注解
// DemoTag 的成员变量有默认值,所以可以不为它的成员变量赋值
@DemoTag
public void info() {
...
}
...
}