22.3 创建注解

22.3 创建注解

框架和库是怎么实现注解的呢?我们来看注解的创建。

我们通过一些例子来说明,先看@Override的定义:

1
2
3
4
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

定义注解与定义接口有点类似,都用了interface,不过注解的interface前多了@。另外,它还有两个元注解@Target@Retention,这两个注解专门用于定义注解本身。@Target表示注解的目标,@Override的目标是方法(ElementType.METHOD)。ElementType是一个枚举,主要可选值有:

  • TYPE:表示类、接口(包括注解),或者枚举声明;
  • FIELD:字段,包括枚举常量;
  • METHOD:方法;
  • PARAMETER:方法中的参数;
  • CONSTRUCTOR:构造方法;
  • LOCAL_VARIABLE:本地变量;
  • MODULE:模块(Java 9引入的)。

目标可以有多个,用{}表示,比如@SuppressWarnings@Target就有多个。Java 7的定义为:

1
2
3
4
5
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}

如果没有声明@Target,默认为适用于所有类型。

@Retention表示注解信息保留到什么时候,取值只能有一个,类型为RetentionPolicy,它是一个枚举,有三个取值。

  • SOURCE:只在源代码中保留,编译器将代码编译为字节码文件后就会丢掉。
  • CLASS:保留到字节码文件中,但Java虚拟机将class文件加载到内存时不一定会在内存中保留。
  • RUNTIME:一直保留到运行时。

如果没有声明@Retention,则默认为CLASS。

@Override和@SuppressWarnings都是给编译器用的,所以@Retention都是Retention-Policy.SOURCE。

可以为注解定义一些参数,定义的方式是在注解内定义一些方法,比如@Suppress-Warnings内定义的方法value,返回值类型表示参数的类型,这里是String[]。使用@Suppress-Warnings时必须给value提供值,比如:

1
@SuppressWarnings(value={"deprecation", "unused"})

当只有一个参数,且名称为value时,提供参数值时可以省略”value=”,即上面的代码可以简写为:

1
@SuppressWarnings({"deprecation", "unused"})

注解内参数的类型不是什么都可以的,合法的类型有基本类型、String、Class、枚举、注解,以及这些类型的数组。

参数定义时可以使用default指定一个默认值,比如,Guice中Inject注解的定义:

1
2
3
4
5
6
@Target({ METHOD, CONSTRUCTOR, FIELD })
@Retention(RUNTIME)
@Documented
public @interface Inject {
boolean optional() default false;
}

它有一个参数optional,默认值为false。如果类型为String,默认值可以为””,但不能为null。如果定义了参数且没有提供默认值,在使用注解时必须提供具体的值,不能为null。

@Inject多了一个元注解@Documented,它表示注解信息包含到生成的文档中。
与接口和类不同,注解不能继承。不过注解有一个与继承有关的元注解@Inherited,它是什么意思呢?我们看个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class InheritDemo {
@Inherited
@Retention(RetentionPolicy.RUNTIME)
static @interface Test {
}
@Test
static class Base {
}
static class Child extends Base {
}
public static void main(String[] args) {
System.out.println(Child.class.isAnnotationPresent(Test.class));
}
}

Test是一个注解,类Base有该注解,Child继承了Base但没有声明该注解。main方法检查Child类是否有Test注解,输出为true,这是因为Test有注解@Inherited,如果去掉,输出会变成false。