注释

 

注释

注释有许多用途,其中包括

  • 编译器信息 - 注释可供编译器用于检测错误或抑制警告。
  • 编译时和部署时处理 - 软件工具可以处理注释信息以生成代码、XML 文件等。
  • 运行时处理 - 一些注释可在运行时进行检查。

本节介绍了注释的应用位置、注释的应用方式、Java 平台标准版 (Java SE API) 中可用的预定义注释类型、类型注释如何与可插拔类型系统结合使用以编写具有更强类型检查的代码,以及如何实现重复注释。

 

注释的格式

在最简单的形式中,注释如下所示

@Entity

at 符号 (@) 表示编译器接下来的是一个注释。在以下示例中,注释的名称是 Override

@Override
void mySuperMethod() { ... }

注释可以包含元素,这些元素可以是命名的或未命名的,并且这些元素有值

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

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

如果只有一个名为value的元素,则可以省略名称,如

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

如果注释没有元素,则可以省略括号,如前面的 @Override 示例所示。

也可以在同一个声明上使用多个注释

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

如果注释类型相同,则称为重复注释

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

从 Java SE 8 版本开始支持重复注释。有关更多信息,请参阅重复注释部分。

注释类型可以是 Java SE API 的 java.langjava.lang.annotation 包中定义的类型之一。在前面的示例中,OverrideSuppressWarnings 是预定义的 Java 注释。也可以定义自己的注释类型。前面的示例中的 AuthorEbook 注释是自定义注释类型。

 

注释的应用位置

注释可以应用于声明:类、字段、方法和其他程序元素的声明。当应用于声明时,每个注释通常按照惯例单独出现在一行上。

从 Java SE 8 版本开始,注释也可以应用于类型的使用。以下是一些示例

  • 类实例创建表达式

    new @Interned MyObject();
    
  • 类型转换

    myString = (@NonNull String) str;
    
  • implements 子句

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

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

这种形式的注释称为类型注释

 

声明注释类型

许多注释替换了代码中的注释。

假设一个软件组传统上在每个类的主体开头添加注释,以提供重要信息

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 前面是 at 符号 (@) (@ = AT,表示注释类型)。注释类型是接口的一种形式,将在后面的部分中介绍。目前,您不需要了解接口。

前面的注释定义的主体包含注释类型元素声明,这些声明看起来很像方法。请注意,它们可以定义可选的默认值。

定义注释类型后,您可以使用该类型的注释,并填写值,如下所示

@ClassPreamble (
   author = "John Doe",
   date = "3/17/2002",
   currentRevision = 6,
   lastModified = "4/12/2004",
   lastModifiedBy = "Jane Doe",
   // Note array notation
   reviewers = {"Alice", "Bob", "Cindy"}
)
public class Generation3List extends Generation2List {

// class code goes here

}

注意:要使 @ClassPreamble 中的信息出现在 Javadoc 生成的文档中,您必须使用 @Documented 注释来注释 @ClassPreamble 定义

// import this to use @Documented
import java.lang.annotation.*;

@Documented
@interface ClassPreamble {

   // Annotation element definitions
   
}

 

预定义注释类型

Java SE API 中预定义了一组注释类型。一些注释类型由 Java 编译器使用,而另一些则应用于其他注释。

Java 语言使用的注释类型

在 java.lang 中定义的预定义注释类型是 @Deprecated@Override@SuppressWarnings.

@Deprecated

@Deprecated 注释表示标记的元素已弃用,不应再使用。每当程序使用带有 @Deprecated 注释的方法、类或字段时,编译器都会生成警告。当元素被弃用时,还应使用 Javadoc @deprecated 标记对其进行文档化,如以下示例所示。在 Javadoc 注释和注释中使用 at 符号 (@) 并非巧合:它们在概念上是相关的。此外,请注意,Javadoc 标记以小写 d 开头,而注释以大写 D 开头。

// Javadoc comment follows
/**
 * @deprecated
 * explanation of why it was deprecated
 */
@Deprecated
static void deprecatedMethod() { }

请注意,从 Java SE 9 开始,forRemoval 属性已添加到 @Deprecated 注释中。它表示注释的元素是否将在未来版本中被删除。默认值为 false

@Override

@Override 注释通知编译器该元素旨在覆盖超类中声明的元素。覆盖方法将在接口和继承部分中讨论。

// mark method as a superclass method
// that has been overridden
@Override 
int overriddenMethod() { }

虽然在覆盖方法时不需要使用此注释,但它有助于防止错误。如果标记为 @Override 的方法未能正确覆盖其超类之一中的方法,则编译器会生成错误。

@SuppressWarnings

@SuppressWarnings 注释告诉编译器抑制它原本会生成的特定警告。在以下示例中,使用了一个已弃用的方法,编译器通常会生成警告。但是,在这种情况下,注释会导致警告被抑制。

// use a deprecated method and tell 
// compiler not to generate a warning
@SuppressWarnings("deprecation")
void useDeprecatedMethod() {
    // deprecation warning
    // - suppressed
    objectOne.deprecatedMethod();
}

每个编译器警告都属于一个类别。Java 语言规范列出了两个类别:弃用和未经检查。未经检查的警告可能发生在与在泛型出现之前编写的旧代码进行交互时。要抑制多个类别的警告,请使用以下语法

@SuppressWarnings({"unchecked", "deprecation"})

@SafeVarargs

@SafeVarargs 注释,当应用于方法或构造函数时,断言代码不会在其 varargs 参数上执行可能不安全的运算。当使用此注释类型时,与 varargs 使用相关的未经检查的警告将被抑制。

@FunctionalInterface

@FunctionalInterface 注释(在 Java SE 8 中引入)表示类型声明旨在成为函数式接口,如 Java 语言规范中所定义。

应用于其他注释的注释

应用于其他注释的注释称为元注释。在 java.lang.annotation 中定义了几个元注释类型。

@Retention

@Retention 注释指定标记的注释的存储方式

@Documented

@Documented 注释表示,无论何时使用指定的注释,都应使用 Javadoc 工具对这些元素进行文档化。(默认情况下,注释不会包含在 Javadoc 中。)有关更多信息,请参阅 Javadoc 工具页面。

@Target

@Target 注释标记另一个注释,以限制可以将该注释应用于哪种 Java 元素。目标注释指定以下元素类型之一作为其值

@Inherited

@Inherited 注释表示注释类型可以从超类继承。(默认情况下并非如此。)当用户查询注释类型并且类没有此类型的注释时,将查询类的超类以获取注释类型。此注释仅适用于类声明。

@Repeatable

@Repeatable 注释(在 Java SE 8 中引入)表示标记的注释可以多次应用于相同的声明或类型使用。有关更多信息,请参阅重复注释部分。

 

类型注释和可插拔类型系统

在 Java SE 8 版本之前,注释只能应用于声明。从 Java SE 8 版本开始,注释也可以应用于任何类型使用。这意味着注释可以在您使用类型的任何地方使用。类型使用的一些示例包括类实例创建表达式 (new)、强制转换、实现子句和抛出子句。这种形式的注释称为类型注释,注释基础部分提供了几个示例。

创建类型注释是为了支持对 Java 程序的改进分析,以确保更强大的类型检查。Java SE 8 版本没有提供类型检查框架,但它允许您编写(或下载)一个类型检查框架,该框架实现为一个或多个可插拔模块,这些模块与 Java 编译器一起使用。

例如,您希望确保程序中的特定变量永远不会被分配为 null;您希望避免触发 NullPointerException。您可以编写一个自定义插件来检查这一点。然后,您将修改代码以注释该特定变量,表明它永远不会被分配为 null。变量声明可能如下所示

@NonNull String str;

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

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

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

在许多情况下,您不必编写自己的类型检查模块。有一些第三方已经为您完成了这项工作。例如,您可能希望利用华盛顿大学创建的 Checker Framework。此框架包括一个 NonNull 模块,以及一个正则表达式模块和一个互斥锁模块。有关更多信息,请参阅 Checker Framework

 

重复注释

在某些情况下,您希望将相同的注释应用于声明或类型使用。从 Java SE 8 版本开始,重复注释使您可以做到这一点。

例如,您正在编写代码以使用计时器服务,该服务使您能够在给定时间或特定时间表上运行方法,类似于 UNIX cron 服务。现在,您希望设置一个计时器来运行方法 doPeriodicCleanup(),在每月的最后一天和每个星期五晚上 11:00 运行。要设置计时器以运行,请创建一个 @Schedule 注释,并将其两次应用于 doPeriodicCleanup() 方法。第一个使用指定每月的最后一天,第二个指定星期五晚上 11 点,如以下代码示例所示

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

前面的示例将注释应用于方法。您可以在使用标准注释的任何地方重复注释。例如,您有一个用于处理未经授权访问异常的类。您使用一个 @Alert 注释为经理注释该类,另一个注释为管理员注释该类

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

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

步骤 1:声明可重复注释类型

注释类型必须用 @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 注释中。

如果在未先声明为可重复的情况下,将相同的注解应用于声明,会导致编译时错误。

步骤 2:声明包含注解类型

包含注解类型必须具有一个value元素,该元素的类型为数组。数组类型的组件类型必须是可重复注解类型。@Schedules包含注解类型的声明如下:

public @interface Schedules {
    Schedule[] value();
}

检索注解

反射 API 中提供了几种可用于检索注解的方法。返回单个注解的方法(例如 AnnotatedElement.getAnnotation(Class<T>))的行为保持不变,即它们仅在存在请求类型的单个注解时才返回该注解。如果存在多个请求类型的注解,则可以通过先获取它们的容器注解来获取它们。这样,旧代码就可以继续工作。Java SE 8 中引入了其他方法,这些方法会扫描容器注解以一次返回多个注解,例如 AnnotatedElement.getAnnotationsByType(Class<T>)。有关所有可用方法的信息,请参阅 AnnotatedElement 类规范。

设计注意事项

在设计注解类型时,必须考虑该类型注解的基数。现在,可以使用注解零次、一次或多次(如果注解的类型被标记为 @Repeatable,则可以使用多次)。还可以使用 @Target 元注解来限制注解类型可以在何处使用。例如,可以创建一个可重复的注解类型,该类型只能用于方法和字段。务必仔细设计注解类型,以确保使用该注解的程序员发现它尽可能灵活和强大。

上次更新: 2021 年 9 月 14 日


返回教程列表