注解是元数据的一种形式,它提供的数据与程序本身无关。注解对它们标注的代码的操作没有直接影响。

注解有许多用途,其中包括:

  • 编译信息检测—编译器能够使用注解来检测错误和警告;
  • 处理编译期信息—软件工具能够将注解信息生成对应代码、xml文件等;
  • 处理运行时信息—一些注解可以在运行时被检测到,进而处理。

注解基本信息(Annotations Basics)

注解格式

注解的简单格式如下:

  1. @Entity

@符号表示这是一个注解,Entity是注解名称。

注解可以包含0至多个元素,这些元素可以是命名的或匿名的,并且这些元素可以有值。例如:

  1. @Author(
  2. name = "Benjamin Franklin",
  3. date = "3/27/2003"
  4. )
  5. class MyClass() { ... }

或者

  1. @SuppressWarnings(value = "unchecked")
  2. void myMethod() { ... }

如果仅有一个名字为value的元素,那么这个元素名称可以忽略。例如:

  1. @SuppressWarnings("unchecked")
  2. void myMethod() { ... }

如果一个注解没有元素,则可以省略括号。例如,注解@Override:

  1. @Override
  2. void mySuperMethod() { ... }

同一个类也可以同时使用多个注解。例如:

  1. @Author(name = "Jane Doe")
  2. @EBook
  3. class MyClass { ... }

如果注解具有相同的类型,则这称为重复注解(Repeating Annotations.):

  1. @Author(name = "Jane Doe")
  2. @Author(name = "John Smith")
  3. class MyClass { ... }

重复注解在Java8之后才支持。更多信息参考Repeating Annotations

注解使用场景

注解能够应用在类、变量、方法以及其他编码元素中。Java8发布之后,注解能够被应用到类型应用上(use of types)。例如:

  • 类实例创建表达式

    1. new @Interned MyObject()
  • 类型转换

    1. myString = (@NonNull String) str;
  • 实现语句块

    1. class UnmodifiableList<T> implements
    2. @Readonly List<@Readonly T> { ... }
  • 抛出异常声明

    1. void monitorTemperature() throws
    2. @Critical TemperatureException { ... }

    这种注解调用形式称之为类型注解(type annotation ).更多信息参考Type Annotations and Pluggable Type Systems.

声明注解类型

编码过程中,很多注解替代了注释。

假设每个类的开始提供了一个注释类标注一些重要的信息:

  1. public class Generation3List extends Generation2List {
  2. // Author: John Doe
  3. // Date: 3/17/2002
  4. // Current revision: 6
  5. // Last modified: 4/12/2004
  6. // By: Jane Doe
  7. // Reviewers: Alice, Bill, Cindy
  8. // class code goes here
  9. }

为了通过一个注解添加同样的元数据,必须声明一个注解类型。语法如下:

  1. @interface ClassPreamble {
  2. String author();
  3. String date();
  4. int currentRevision() default 1;
  5. String lastModified() default "N/A";
  6. String lastModifiedBy() default "N/A";
  7. // Note use of array
  8. String[] reviewers();
  9. }

注解声明开起来和接口声明差不多,只不过在interface前增加了@注解符号。注解是接口的一种形式,后面会再次提到。

注解主体部分包含了注解类型元素声明(看起来像是方法声明)。注意:可以赋以默认值。

在注解声明之后,可以在类中进行使用:

  1. @ClassPreamble (
  2. author = "John Doe",
  3. date = "3/17/2002",
  4. currentRevision = 6,
  5. lastModified = "4/12/2004",
  6. lastModifiedBy = "Jane Doe",
  7. reviewers = {"Alice", "Bob", "Cindy"}
  8. )
  9. public class Generation3List extends Generation2List {
  10. }

预定义注解类型

Java中预设了部分注解。一些注解使用在编译器,一些使用在别的注解上。

Java中预设注解

预设的注解类型定义在java.lang包下,分别是@Deprecated@Override以及@SuppressWarnings

@Deprecated

@Deprecated表示所标注元素已过时且不应该再被使用。当使用该注解标注的元素时,编译器会生成一个警告。当一个元素被声明为已过时,那么对应的文档也应该添加@Deprecated标签。

  1. /**
  2. * @deprecated // 文档中为小写的d开头
  3. * explanation of why it was deprecated
  4. */
  5. @Deprecated
  6. static void deprecatedMethod() { }

@Override

@Override表示该元素是重写父类的元素。

注意:该元素可以帮助开发者避免错误。例如,子类重写了父类的A方法,如果不添加@Override,那么当父类的A方法重命名时,子类不会报错,但是调用逻辑会发生改变。所以重写的方法一定要用@Overrid标注,避免异常发生。

@SuppressWarnings

@SuppressWarnings注解告诉编译器忽略警告。例如,如果使用了一个过时的方法,编译器会产生一个警告。但是可以使用该注解忽略这个警告:

  1. @SuppressWarnings("deprecation")
  2. void useDeprecatedMethod() {
  3. // deprecation warning
  4. // - suppressed
  5. objectOne.deprecatedMethod();
  6. }

每一个编译器的警告都属于一个类别。Java Language Specification中列出了两个类别:deprecateionunchecked。当与泛型出现之前编写的遗留代码交互时,可能会出现unchecked警告。要忽略多个类别的警告,请使用以下语法:

  1. @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。变量声明可能如下所示:

  1. @NonNull String str;

编译代码(包括命令行中的非空模块)时,如果编译器检测到潜在问题,则会打印警告,允许开发者修改代码以避免错误。更正代码以删除所有警告后,程序运行时不会发生此特定错误。

可以使用多个类型检查模块,其中每个模块检查不同类型的错误。通过这种方式,您可以在Java类型系统的基础上进行构建,在需要的时间和地点添加特定的检查。

通过明智地使用类型注释和可插入类型检查器,您可以编写更强大、更不容易出错的代码。

在许多情况下,开发者不必编写自己的类型检查模块。有第三方为你做了这项工作。例如,您可能希望利用华盛顿大学创建的Checker框架。该框架包括一个非空模块、一个正则表达式模块和一个互斥锁模块。有关更多信息,请参见Checker框架

Repeating Annotations

当你项在一个类型上使用多次同一注解时,Java8提供的Repeating Annotations可以做到这一点。

例如:你想编写代码实现定时服务。现在你想设置一个定时器来运行一个方法doPeriodicCleanup,在每月的最后一天每周五的11:00 p.m运行定时器,请创建一个@Schedule注解并将其应用于doPeriodicCleanup方法两次。第一种用法指定一个月的最后一天,第二种用法指定星期五晚上11点:

  1. @Schedule(dayOfMonth="last")
  2. @Schedule(dayOfWeek="Fri", hour="23")
  3. public void doPeriodicCleanup() { ... }

上一个示例将注解应用于方法。可以在任何使用标准注解的地方重复注解。例如,您有一个类用于处理未经授权的访问异常。您可以使用一个针对管理者的@Alert注解和另一个针对管理员的@Alert注解对类进行注解:

  1. @Alert(role="Manager")
  2. @Alert(role="Administrator")
  3. public class UnauthorizedAccessException extends SecurityException { ... }

出于兼容性原因,重复注解存储在由Java编译器自动生成的注解容器中。为了让编译器做到这一点,代码中需要两个声明。

具体实例参考这里

声明可重复注解类型

这个注解类必须被@Repeatable元注解标注。下例声明一个可重复注解@Schedule:

  1. @Repeatable(Schedules.class)
  2. public @interface Schedule {
  3. String dayOfMonth() default "first";
  4. String dayOfWeek() default "Mon";
  5. int hour() default 12;
  6. }

括号中的是@Repeatable元注解的值,表示的是Java编译器生成的用来存储可重复注解的容器类型。本例中,注解容器类型是Schedules。因此,可重复注解@Schedule存储在@Schedules注解中。

注意:应用相同的注解而不首先声明它是可重复的,会导致编译时错误。

声明注解容器类型

注解容器类型必须有一个数组类型的元素。该数组的元素类型必须是一个可重复注解类型。例如:

  1. public @interface Schedules {
  2. Schedule[] value();
  3. }

获取注解

反射API中有几种方法可用于获取注解,例如AnnotatedElement.getAnnotation(Class)在只存在一种注解的只返回一个注解。如果存在多种请求类型的注解,则可以通过首先获取它们的容器注释来获取它们。

JavaSE8中引入了AnnotatedElement.getAnnotationsByType(Class)方法,该方法可以扫描注解容器并一次返回多个注释。

设计注意事项

在设计注解类型时,必须考虑该类型注解的基数。可以使用注解零次,一次,或者如果注解的类型被标记为@Repeatable,则可以多次使用注解。还可以通过使用@Target元注解来限制注解类型的使用位置。例如,可以创建只能在方法和字段上使用的可重复注解类型。设计注解类型非常重要,以确保使用注解的程序员发现它尽可能灵活和强大。