注解Annotations
1 什么是注解
注解是代码中的特殊标记,具有以下作用:
为编译器提供信息。编译器能够使用注解来检测错误或抑制警告信息,例如
- 编译器能够使用注解
@Override
检测对应代码是否正确实现对应重写; - 编译器能够使用注解
@SuppressWarnings
抑制对应代码产生的警告信息,即使其不产生对应警告信息。
- 编译器能够使用注解
- 作为编译时和部署时处理。软件工具能够处理注解信息以生成代码、XML文件等。
- 作为运行时处理。可以在运行时对一些注解进行检查。
2 注解的形式
注解的形式可以分为三类(以下所提及的元素类似于类中的成员变量):
- 不包含元素的注解。最简单的注解即不包含元素的注解:
@注解名
。 包含元素的注解。包含元素的注解可以分为:
- 具有多个元素的注解:
@注解名(元素1名=元素1值, 元素2名=元素2值, ...)
- 具有1个元素的注解,或者说只有一个名为
value
元素的注解:@注解名(value元素的值)
,注意如果value
元素为数组类型,则其值可能有多个。只有一个元素时,对应的元素名可以省略。
- 具有多个元素的注解:
- 重复的注解(JDK8时引入)
有时会出现重复的同一种注解的形式的使用:
3 注解的使用
JDK8之前,注解可以应用在各种声明(或者说定义)上:类、属性、方法等结构的声明。
注解应用在声明上时,通常一个注解单独占一行:
JDK8时,注解可以应用在类型的使用上,可以简单理解为配合各种类型使用,这种形式的注解称为类型注解type annotation
。
4 自定义注解(注解类型的声明)
自定义注解的语法为:
@interface 注解名{
元素的定义
}
定义的注解的注解体中可以对注解对应元素进行声明,元素的定义语法为:元素类型 元素名()
,有点像方法。
注意,可以为每个定义的元素设置默认值,语法为:元素类型 元素名() default 元素默认值
。
通过对定义好的注解对应元素赋值,即可使用该注解。
5 预定义的注解
预定义的注解可以分为2大类:
- 用于Java语言的;
- 用于注解的。
1) 用于Java语言的预定义注解
用于Java语言中的预定义的注解在java.lang
包下,包括@Deprecated
、@Override
、@SuppressWarnings
、@SafeVarargs
和@FunctionalInterface
。
@Deprecated
- 作用:该注解指示被标记的元素或者说结构是过时的,不应该再被使用。
- 使用效果:如果使用了被该注解标记的方法、类或属性时,编译器会产生一个警告。
- 注意点:1) 如下图所示,用该注解标记一个元素时,该元素对应的文档注释中应该用JavaDoc的
@deprecated
标签标记。2) JDK9时,@Deprecated
注解新增了一个forRemovel
属性,用于指示被标记的元素是否会在未来被移除,默认值是false
。
@Override
- 作用:告知编译器,被其标记的元素或者说方法为对应父类的重写元素或者说方法,能够防止重写错误。
- 使用效果:被该注解标记的方法如果没有正确实现重写,编译器会产生一个错误。
@SuppressWarnings
- 作用:被该注解标记的元素,如果(在编译时)产生警告,编译器会抑制对应警告。
- 使用效果:编译器使得产生警告的被标记元素不产生对应警告。
- 注意点:每个编译器警告都属于一个类别。Java语言规范列出了2个类别:
弃用deprecated
和未检查unchecked
。要抑制多个类别的警告,可以使用以下语法:
@SafeVarargs
- 作用:该注解在应用于方法或构造器时,声明对应代码不会对其
varargs
(可变参数)执行潜在的不安全操作。 - 使用效果:当使用该注解时,与可变参数有关的未检查警告会被抑制。
- 作用:该注解在应用于方法或构造器时,声明对应代码不会对其
@FunctionalInterface
- 作用:该注解表明类型打算声明为一个函数式接口。
2) 用于注解的预定义注解
用于注解的预定义注解也被称为元注解,位于java.lang.annotation
包下,包括@Retention, @Documented, @Target, @Inherited, @Repeatable
。
@Retention:该注解能够指定被其标记的注解的保存,可以理解为注解的生命周期:
RetentionPolicy.SOURCE
:指定被标记的注解只保留在源码中,编译器会忽略。RetentionPolicy.CLASS
:指定被标记的注解在被编译器编译时也会保留,JVM会忽略。(默认)RetentionPolicy.RUNTIME
:指定被标记的注解在JVM中也会保留,即可以在运行时使用。
- @Documented:该注解能够使得被标记的注解会保存在对应的Java文档中,比如
@Deprecated
。 @Target:该注解能够指定被其标记的注解能够应用在哪些Java元素(或者说结构)上:
- ElementType.ANNOTATION_TYPE:表示可以被应用到注解上;
- ElementType.CONSTRUCTOR:表示可以被应用到构造器上;
- ElementType.FIELD:表示可以被应用到属性上;
- ElementType.LOCAL_VARIABLE:表示可以被应用到局部变量上;
- ElementType.METHOD:表示可以被应用到方法上;
- ElementType.MODULE:表示可以被应用到模块的声明上;
- ElementType.PACKAGE:表示可以被应用到包的声明上;
- ElementType.PARAMETER:表示可以被应用到方法的参数上;
- ElementType.RECORD_COMPONENT:表示可以被应用到记录的组件上;
- ElementType.TYPE:表示可以被应用到类、抽象类、接口、注解接口、枚举类或者记录声明上;
- ElementType.TYPE_PARAMETER:表示可以被应用到类型的参数上(如泛型声明);
- ElementType.TYPE_USE:表示可以被应用到类型的使用上,比如类的实例化。
- @Inherited:该注解能够使得被标记的注解具有继承性。如果某个类使用了被该注解标记的注解,其子类将自动具有对应注解。
- @Repeatable:JDK8时引入,该注解能够使得被标记的注解能够多次应用于同一个声明或类型使用上。
6 重复注解
有些场景下需要使用重复注解,比如编写一个定时服务,你需要在两个时间点进行定时,又比如授权服务,需要给多个用户进行授权。
JDK8时引入了定义重复注解的标准:
- 首先对要重复的注解用
@Repeatable()
元注解标记,其中括号中就是指定对该注解进行重复使用的容器。容器实际上就是另一个注解,在@Repeatable()
括号中的格式为:注解名.class
- 然后定义容器注解。定义容器注解就是定义一个以要重复的注解类型为类型的元素数组的元素的注解。
注意,重复注解和容器注解的各元注解的设定要一致。
7 类型注解和可插拔类型系统(Type Annotations and Pluggable Type Systems)
JDK8时能够支持注解应用在任何类型上,这使得任何使用类型的地方都可以应用注解,比如类的实例化时new
后面的类型的前面等。这种形式的注解称为类型注解。
创建类型注释是为了支持改进的Java程序分析,从而确保更强的类型检查。
Java SE 8版本不提供类型检查框架,但它允许编写(或下载)一个类型检查框架,该框架被实现为一个或多个可插入模块,这些模块与Java编译器一起使用。
例如,希望确保程序中的某个特定变量永远不会被赋值为null,希望避免触发NullPointerException,可以编写一个自定义插件来检查这一点。然后,修改代码用注解标记该特定变量,表明它永远不会被赋值为null。
当编译代码(包括命令行中的NonNull模块)时,如果编译器检测到潜在的问题,它将打印一条警告,从而允许你修改代码以避免错误。在更正代码以删除所有警告之后,在程序运行时将不会发生此特定错误。
可以使用多个类型检查模块,其中每个模块检查不同类型的错误。通过这种方式,可以在Java类型系统的基础上进行构建,在需要的时间和地方添加特定的检查。
通过明智地使用类型注解和可插入类型检查器,可以编写更强大、更不容易出错的代码。
在许多情况下,不必编写自己的类型检查模块。有第三方完成这项工作。