注解是元数据的一种形式,它提供的数据与程序本身无关。注解对它们标注的代码的操作没有直接影响。
注解有许多用途,其中包括:
- 编译信息检测—编译器能够使用注解来检测错误和警告;
- 处理编译期信息—软件工具能够将注解信息生成对应代码、xml文件等;
- 处理运行时信息—一些注解可以在运行时被检测到,进而处理。
注解基本信息(Annotations Basics)
注解格式
注解的简单格式如下:
@Entity
@
符号表示这是一个注解,Entity
是注解名称。
注解可以包含0至多个元素,这些元素可以是命名的或匿名的,并且这些元素可以有值。例如:
@Author(
name = "Benjamin Franklin",
date = "3/27/2003"
)
class MyClass() { ... }
或者
@SuppressWarnings(value = "unchecked")
void myMethod() { ... }
如果仅有一个名字为value的元素,那么这个元素名称可以忽略。例如:
@SuppressWarnings("unchecked")
void myMethod() { ... }
如果一个注解没有元素,则可以省略括号。例如,注解@Override:
@Override
void mySuperMethod() { ... }
同一个类也可以同时使用多个注解。例如:
@Author(name = "Jane Doe")
@EBook
class MyClass { ... }
如果注解具有相同的类型,则这称为重复注解(Repeating Annotations.):
@Author(name = "Jane Doe")
@Author(name = "John Smith")
class MyClass { ... }
重复注解在Java8之后才支持。更多信息参考Repeating Annotations。
注解使用场景
注解能够应用在类、变量、方法以及其他编码元素中。Java8发布之后,注解能够被应用到类型应用上(use of types)。例如:
类实例创建表达式
new @Interned MyObject()
类型转换
myString = (@NonNull String) str;
实现语句块
class UnmodifiableList<T> implements
@Readonly List<@Readonly T> { ... }
抛出异常声明
void monitorTemperature() throws
@Critical TemperatureException { ... }
这种注解调用形式称之为类型注解(type annotation ).更多信息参考Type Annotations and Pluggable Type Systems.
声明注解类型
编码过程中,很多注解替代了注释。
假设每个类的开始提供了一个注释类标注一些重要的信息:
public class Generation3List extends Generation2List {
// Author: John Doe
// Date: 3/17/2002
// Current revision: 6
// Last modified: 4/12/2004
// By: Jane Doe
// Reviewers: Alice, Bill, Cindy
// class code goes here
}
为了通过一个注解添加同样的元数据,必须声明一个注解类型。语法如下:
@interface ClassPreamble {
String author();
String date();
int currentRevision() default 1;
String lastModified() default "N/A";
String lastModifiedBy() default "N/A";
// Note use of array
String[] reviewers();
}
注解声明开起来和接口声明差不多,只不过在interface
前增加了@
注解符号。注解是接口的一种形式,后面会再次提到。
注解主体部分包含了注解类型元素声明(看起来像是方法声明)。注意:可以赋以默认值。
在注解声明之后,可以在类中进行使用:
@ClassPreamble (
author = "John Doe",
date = "3/17/2002",
currentRevision = 6,
lastModified = "4/12/2004",
lastModifiedBy = "Jane Doe",
reviewers = {"Alice", "Bob", "Cindy"}
)
public class Generation3List extends Generation2List {
}
预定义注解类型
Java中预设了部分注解。一些注解使用在编译器,一些使用在别的注解上。
Java中预设注解
预设的注解类型定义在java.lang
包下,分别是@Deprecated
、@Override
以及@SuppressWarnings
。
@Deprecated
@Deprecated
表示所标注元素已过时且不应该再被使用。当使用该注解标注的元素时,编译器会生成一个警告。当一个元素被声明为已过时,那么对应的文档也应该添加@Deprecated
标签。
/**
* @deprecated // 文档中为小写的d开头
* explanation of why it was deprecated
*/
@Deprecated
static void deprecatedMethod() { }
@Override
@Override
表示该元素是重写父类的元素。
注意:该元素可以帮助开发者避免错误。例如,子类重写了父类的A方法,如果不添加@Override,那么当父类的A方法重命名时,子类不会报错,但是调用逻辑会发生改变。所以重写的方法一定要用@Overrid
标注,避免异常发生。
@SuppressWarnings
@SuppressWarnings
注解告诉编译器忽略警告。例如,如果使用了一个过时的方法,编译器会产生一个警告。但是可以使用该注解忽略这个警告:
@SuppressWarnings("deprecation")
void useDeprecatedMethod() {
// deprecation warning
// - suppressed
objectOne.deprecatedMethod();
}
每一个编译器的警告都属于一个类别。Java Language Specification中列出了两个类别:deprecateion
和 unchecked
。当与泛型出现之前编写的遗留代码交互时,可能会出现unchecked警告。要忽略多个类别的警告,请使用以下语法:
@SuppressWarnings({"unchecked", "deprecation"})
@SafeVarargs
@SafeVarargs注释应用于方法或构造函数时,断言代码不会对其varargs参数执行潜在的不安全操作。使用此批注类型时,将抑制与varargs用法相关的未选中警告。
@FunctionalInterface
@FunctionalInterface 出现在Java8,表示这是一个函数式接口。
应用在其他注解上的注解
@Retention
@Retention注解指定如何存储标记的注解:
- RetentionPolicy.SOURCE – 标记的注解仅保留在源代码级别,编译器将忽略它。
- RetentionPolicy.CLASS – 标记的注解在编译时由编译器保留,但被Java虚拟机(JVM)忽略。
- RetentionPolicy.RUNTIME – 标记的注解由JVM保留,以便运行时环境可以使用它。
@Documented
@Documented 表示每当使用指定的注解时,应该使用Javadoc工具记录这些元素。(默认情况下,注解不包括在Javadoc中。)有关更多信息,请参阅Javadoc工具页。
@Target
@Target标记另一个注解,以限制该注释可以应用于哪种Java元素。目标注解指定以下元素类型之一作为其值:
- ElementType.ANNOTATION 可以应用于注解类型
- ElementType.CONSTRUCTOR 可以应用于构造函数
- ElementType.FIELD 可以应用于字段或属性
- ElementType.LOCAL_VARIABLE 可以应用于局部变量
- ElementType.METHOD 可以应用于方法级注释
- ElementType.PACKAGE 可以应用于包声明
- ElementType.PARAMETER 可以应用于方法的参数
- ElementType.TYPE 可以应用于类或更多信息
@Inherited
@Inherited注解表示可以从超类继承注解类型(默认为false)。当用户查询注解类型而类没有此类型的注解时,将查询类的超类以获取注解类型。此注解仅适用于类声明。
**
@Repeatable
@Repeatable 是 JavaSE8 中引入的可重复注解声明,标记的注解可以多次应用于同一声明或类型使用。更改信息参考Repeating Annotations
类型注解和可注入类型系统
在Java8之前,注解只能用于声明。在Java8之后,注解也能够应用于任何类型的使用(type use)。这就意味着注解能够使用在使用了任意类型的地方。类型的使用实例有类实例创建表达式(new)、cast、implements子句和throws子句。这种形式的注解称为类型注解,Annotations Basics中提供了几个示例。
创建类型注解是为了支持改进的Java程序分析,从而确保更强的类型检查。JavaSE8版本没有提供类型检查框架,但是它允许开发者编写(或下载)一个类型检查框架,该框架被实现为一个或多个与Java编译器结合使用的可插入模块。
例如,如果希望确保程序中的某个特定变量不会赋值为null;或者希望避免触发NullPointerException。可以编写一个自定义插件来检查这一点。然后修改代码以注解该特定变量,表明该变量不会赋值为null。变量声明可能如下所示:
@NonNull String str;
编译代码(包括命令行中的非空模块)时,如果编译器检测到潜在问题,则会打印警告,允许开发者修改代码以避免错误。更正代码以删除所有警告后,程序运行时不会发生此特定错误。
可以使用多个类型检查模块,其中每个模块检查不同类型的错误。通过这种方式,您可以在Java类型系统的基础上进行构建,在需要的时间和地点添加特定的检查。
通过明智地使用类型注释和可插入类型检查器,您可以编写更强大、更不容易出错的代码。
在许多情况下,开发者不必编写自己的类型检查模块。有第三方为你做了这项工作。例如,您可能希望利用华盛顿大学创建的Checker框架。该框架包括一个非空模块、一个正则表达式模块和一个互斥锁模块。有关更多信息,请参见Checker框架。
Repeating Annotations
当你项在一个类型上使用多次同一注解时,Java8提供的Repeating Annotations可以做到这一点。
例如:你想编写代码实现定时服务。现在你想设置一个定时器来运行一个方法doPeriodicCleanup
,在每月的最后一天和每周五的11:00 p.m运行定时器,请创建一个@Schedule注解并将其应用于doPeriodicCleanup
方法两次。第一种用法指定一个月的最后一天,第二种用法指定星期五晚上11点:
@Schedule(dayOfMonth="last")
@Schedule(dayOfWeek="Fri", hour="23")
public void doPeriodicCleanup() { ... }
上一个示例将注解应用于方法。可以在任何使用标准注解的地方重复注解。例如,您有一个类用于处理未经授权的访问异常。您可以使用一个针对管理者的@Alert注解和另一个针对管理员的@Alert注解对类进行注解:
@Alert(role="Manager")
@Alert(role="Administrator")
public class UnauthorizedAccessException extends SecurityException { ... }
出于兼容性原因,重复注解存储在由Java编译器自动生成的注解容器中。为了让编译器做到这一点,代码中需要两个声明。
具体实例参考这里
声明可重复注解类型
这个注解类必须被@Repeatable
元注解标注。下例声明一个可重复注解@Schedule
:
@Repeatable(Schedules.class)
public @interface Schedule {
String dayOfMonth() default "first";
String dayOfWeek() default "Mon";
int hour() default 12;
}
括号中的是@Repeatable元注解的值,表示的是Java编译器生成的用来存储可重复注解的容器类型。本例中,注解容器类型是Schedules
。因此,可重复注解@Schedule
存储在@Schedules
注解中。
注意:应用相同的注解而不首先声明它是可重复的,会导致编译时错误。
声明注解容器类型
注解容器类型必须有一个数组类型的元素。该数组的元素类型必须是一个可重复注解类型。例如:
public @interface Schedules {
Schedule[] value();
}
获取注解
反射API中有几种方法可用于获取注解,例如AnnotatedElement.getAnnotation(Class
JavaSE8中引入了AnnotatedElement.getAnnotationsByType(Class
设计注意事项
在设计注解类型时,必须考虑该类型注解的基数。可以使用注解零次,一次,或者如果注解的类型被标记为@Repeatable,则可以多次使用注解。还可以通过使用@Target元注解来限制注解类型的使用位置。例如,可以创建只能在方法和字段上使用的可重复注解类型。设计注解类型非常重要,以确保使用注解的程序员发现它尽可能灵活和强大。