元注解,英文名称Meta Annotation,是作用于注解的注解。换言之,元注解是用于声明,创建注解的时候用到的标记,符号。Jdk 在java.lang包下提供了五个基本注解(基本注解章节的五个注解)的同时,又在java.lang.annotation包下提供了六个元注解。这个六个元注解中只有五个元注解可以用于修饰其它注解,较为特殊的一个则是Java 8新增的重复注解。
这六个元注解分别是
- @Retention
- @Target
- @Documented
- @Inherited
- @Repeatable
@Retention
设想一下,你要创建一个注解给其它开发者使用。正如你创建一个类给其它开发者使用一样,你必须指定该类的某些规则,比如指定该类只能连接MySQL数据库。注解也一样,在创建的时候需要指定该注解的某些规则,比如可以保留多长时间。
@Retention注解就是在声明创建一个注解的时候,指定这个被声明创建的注解可以保留多长时间。它里边有一个RetentionPolicy类型的value变量,该变量的值只有三个,不同的值代表不同的保留规则。
具体保留规则如下:
- RetentionPolicy.CLASS
编译器将注解记录在.class文件中。当运行Java程序时,JVM不能获取注解信息。这是默认值
- RetentionPolicy.RUNTIME
编译器将注解记录在.class文件中。当运行Java程序时,JVM也能获取注解信息。程序可以通过反射获取注解信息。
- RetentionPolicy.SOURCE
编译器直接丢弃注解。所以该注解只能保留在源代码文件中。
注意,使用@Retention注解必须为value变量赋值。
示例代码
//创建声明Test注解,该注解被使用的时候只能可以保留到运行阶段
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Test {}
//创建声明Test注解,该注解编译器将直接丢弃,所以只能保留在源代码中
@Retention(RetentionPolicy.SOURCE)
public @interface Test{}
总结 : 声明创建注解要使用@interface这种格式
使用注解的时候,如果只需要给注解的value属性赋值,直接将value属性的值写出即可,无需再使用key=value形式。
@Target
@Target注解是在声明创建一个注解的时候,指示该注解可以作用于程序中的哪些元素。它里边也包含一个名为value的成员变量,value成员变量的值有如下几种
- ElementType.ANNOTATION_TYPE:指定当前注解只能修饰其它注解
- ElementType.CONSTRUCTOR:指定当前注解只能修饰构造方法
- ElementType.FIELD:指定当前注解只能修饰成员变量
- ElementType.LOCAL_VARIABLE:指定当前注解只能修饰局部变量
- ElementType.METHOD:指定当前注解只能修饰方法
- ElementType.PACKAGE:指定当前注解只能修饰包
- ElementType.PARAMETER:指定当前注解只能修饰参数
- ElementType.TYPE:指定当前注解可以修饰类,接口,其它注解,枚举等类型
示例代码
//创建声明Test注解,该注解只能修饰成员变量
@Target(ElementType.FIELD)
public @interface Test {}
//创建声明Test注解,该注解只能修饰方法
@Target(ElementType.METHOD)
public @interface Test {}
@Documented
@Documented元注解是在声明创建一个注解的时候,指示该注解将被javadoc工具提取成文档。这样所有使用该注解的程序元素的API文档中都会包含该注解说明。
示例代码
声明一个使用@Documented注解的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface MyDocument {}
声明一个测试类,该类使用了@MyDocument注解
public class TestDocument {
@MyDocument
public void test() {}
}
效果图
可以看到,javadoc提取TestDocument.java成API文档的时候,将注解@MyDocument的说明信息也提取到了文档中。
@Inherited
@Inherited注解是在声明创建一个注解的时候,指定该注解将具有继承性:如果某个类使用了该注解,则其子类也将自动被改注解所修饰。
示例代码
声明创建一个注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface MyInherited {}
创建一个父类
@MyInherited
public class MyInheritedTestFu {}
所有的子类都将自动将@MyInherited注解给继承
创建子类进行测试
public class MyInheritedTestZi extends MyInheritedTestFu {
public static void main(String[] args) {
boolean a = MyInheritedTestZi.class.isAnnotationPresent(MyInherited.class);
System.out.println(a);
}
}
打印结果为true,可以看到虽然子类没有使用@MyInherited注解,却依然是被@MyInherited注解所修饰。如果将@MyInherited
的@Inherited元注解去掉,那么子类将无法从父类继承@MyInherited注解。打印结果将会是false.
@Repeatable
某些情况下,一个程序元素需要使用多个同一种类型的注解。@Repeatable元注解,顾名思义,重复注解,就是在声明创建注解的时候,指定该注解可以被同一个程序元素多次使用。
例如一个公司的创始人,它既是CEO,又是产品经理,还要负责财务,招聘等等,具有多种角色身份。这种情况就可以使用重复注解。
示例代码
声明注解和注解容器
/* 声明注解,该注解可以被程序元素重复使用 */
@Repeatable(Roles.class)
public @interface Role {
String value();
}
/* 定义注解"容器" : Roles里边是Role的数组,这样Roles里边就可以存放多个Role */
public @interface Roles {
Role[] value();
}
使用重复注解
@Role("CEO")
@Role("UFO")
public class People { }
注意 :
- “容器”注解Roles的保留期必须比它所包含的注解Role的保留期更长,否则编译器会报错。
- 重复注解是Java 8新增的元注解
- Java 8以前不允许重复注解,所以之前都只能用如下形式来表达
//直接使用Roles来盛装Role
@Roles({ @Role("CEO"), @Role("UFO") })
public class People {}
- 重复注解不是一个语言上的改变,只是编译器层面的改动,技术层面仍然是一样的。其本质依然是多个重复注解作为了“容器”注解的value成员变量的数组元素。本质代码请看注意3