当前位置:   article > 正文

Annotations_定义一个名为enhancement的注解类型,包含id、synopsis、engineer和date

定义一个名为enhancement的注解类型,包含id、synopsis、engineer和date等4个元素,

课程:Annotations

注释(annotations)是一种诠释数据,提供与程序相关但不属于程序本身的信息,不直接影响它标注的代码。

注释有以下作用:

  • 为编译器提供信息——被编译器用来探测错误或抑制警报
  • 编译时和部署时加工——软件工具可以通过加工注释信息来生成代码、XML文件等等
  • 运行时加工:一些注释允许在运行时被检索

这个课程解释了哪里可以使用注释,如何应用注释,Java SE API中有哪些可用预定义注释,类型注释如何被用来与可插入类型系统的结合使用,以编写具有更强类型检查的代码。

注释基础

注释的格式
最简单的如:

@Entity
  • 1

@符号告诉编译器接下来的内容为一个注释,以下例子里,注释的名字是Override:

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

注释可以包含有名或无名的元素,元素拥有值:

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

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

如果只有一个叫value的元素,元素名字可以省略,比如:

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

如果注释没有元素,那么括号可以省略,前面的@Override例子中已经展示。

同一个声明上可以有多重注释:

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

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

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

Java SE 8推出后支持重复注释,更多的信息可以在Repeating Annotations中查看。

注释类型可以是Java SE API中的java.lang包或java.lang.annotation包中定义的类型之一。在前面的例子中,Override和SuppressWarnings都是预定义Java注释。自定义注释类型也是可行的,前面例子中的Author注释和Ebook注释都是自定义注释类型。

注释使用的位置
注释可以应用于声明:类的声明,变量的声明,方法的声明,以及其他程序元素。按照惯例,当注释在声明上使用时,每个注释都拥有独立的行。
Java SE 8推出后,注释看在应用于类型的使用,下面有些例子:

  • 类实例创建表达式:
new @Interned MyObject();
  • 1
  • 类型转换:
myString = (@NonNull String) str;
  • 1
  • implements子句:
class UnmodifiableList<T> implements
    @Readonly List<@Readonly T> { ... }
  • 1
  • 2
  • 被抛出异常的声明:
void monitorTemperature() throws
    @Critical TemperatureException { ... }
  • 1
  • 2

这种形式的注释被称为类型注释,想了解更多请浏览Type Annotations and Pluggable Type Systems。

声明一个注释类型

在代码中,很多注释取代了注解(commets)。

假设一个软件团队习惯于在每个类体开头写注解以提供重要信息:

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

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

为了以注释的形式添加相同的诠释数据,您首先必须定义注释类型,语法为:

@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();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

注释类型定义看起来与interface定义相似 ,关键字interface被添加了@前缀。注释类型是interface的一种形式,之后的课程中会讨论。现在不必理解interface。

之前的注释定义体包含了看起来很像方法声明的注释类型元素声明。注意,注释元素可以定义默认值。

注释类型被定义后,您可以使用该注释类型的注释,并赋值,像这样:

@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

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

笔记:为了让@ClassPreamble中的信息出现在Javadoc生成文档,您必须在定义@ClassPreamble时添加@Documented注释

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

@Documented
@interface ClassPreamble {

   // Annotation element definitions
   
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

预定义注解类型

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

被Java语言使用的注释类型
在java.lang包中预定义的注释类型有@Deprecated,@Override和@SuppressWarnings。

@Deprecated @Deprecated注释表明被标记的元素被弃用,不应该再使用。当程序使用一个带了@Annotation注释的方法,类或变量,编译器会生成一个警告。当一个元素被弃用,它也应该被在注解Javadoc时添加@deprecated标签(tag),正如以下例子中展示的。Javadoc注解(comments)和注释中都使用@符号并不是巧合:它们在概念上是相关的。另外,注意Javadoc标签以小写d开头而注释以大写D开头。

   // Javadoc comment follows
    /**
     * @deprecated
     * explanation of why it was deprecated
     */
    @Deprecated
    static void deprecatedMethod() { }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

@Override @Override注释告诉编译器被注释元素的目的是重写(override)一个父类中声明的元素。重写方法将在Interfaces and Inheritance中讨论。

   // mark method as a superclass method
   // that has been overridden
   @Override 
   int overriddenMethod() { }
  • 1
  • 2
  • 3
  • 4

虽然重写方法时不一定要使用这个注释,但它有助于防止错误。如果一个标了@Override的方法无法正确地重写它父类中地某个方法,编译器就会报错。

@SuppressWarnings @SuppressWarnings注释告诉编译器去抑制特定的将会额外生成的警告(warnings),在以下例子中,一个弃用方法被使用,编译器生成了一个警告。然而在这个例子中,@SuppressWarnings将使警告被抑制。

   // use a deprecated method and tell 
   // compiler not to generate a warning
   @SuppressWarnings("deprecation")
    void useDeprecatedMethod() {
        // deprecation warning
        // - suppressed
        objectOne.deprecatedMethod();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

每个编译器警告属于一个种类。Java语言说明书列出了两个种类:deprecation和unchecked。在与泛型(generics)出现前的老代码交互可能会出现unchecked警告。为了抑制多种类型的警告,使用以下语法:

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

@SafeVarargs @SafeVarargs注释,当应用于一个方法或构造器,断言代码不会在它的可变参数(varargs)上执行潜在的不安全操作,关于使用可变参数的未经检查警告(unchecked warning)被抑制。

@FunctionallInterface @FunctionalInterface注释,在Java SE 8中已经介绍,指明了类型的声明是为了成为函数式接口,在Java语言说明书(Java Language Specification)中被定义。

应用于其他注释的注释

被应用于其他注释的注释被成为诠释注释(meta-annotations)。这里有几个在java.lang.annotation包中定义的诠释注释。
@Retention @Retention 注释指定了被标记注释如何存储:

  • RetentionPolicy.SOURCE——被标记注释只在源级别保持(retain),会被编译器忽略。
  • RetentionPolicy.CLASS——被标记注释在编译时被编译器保持,但被JVM忽视。
  • RetentionPolicy.RUNTIME——被标记注释在JVM中保持,所以可在运行时环境(runtime environment)被使用。

@Documented @Documented注释表明无论何时指定注释被使用,那些元素(elements)都应该被使用Javadoc工具文档化。(默认情况下,注释不包含在Javadoc中)。想了解更多,请看Javadoc tools page

@Target @Target注释标记了其他注释以限制该注释可以应用于哪些类型的Java元素。一个target注释指定了以下元素类型之一作为它的值:

  • ElementType.ANNOTATION_TYPE 可以用于注释类型。

  • ElementType.CONSTRUCTOR可以用于构造器。

  • ElementType.FIELD可以用于变量或属性(property)。

  • ElementType.LOCAL_VARIABLE可以用于局部
    变量。

  • ElementType.METHOD 可用于方法级别(method-level )注释。

  • ElementType.PACKAGE可用于包声明。

  • ElementType.PARAMETER可用于方法参数。

  • ElementType.TYPE可用于类的任何元素。

@Inherited@Inherited注释表明注释类型可从超类中继承。(默认情况下不行。)当用户查询注释类型且类中没有该注释类型时,这个类的超类会被查询。这个注释只能用于类声明。

@Repeatable@Repeatable注释,在Java SE 8中有介绍,表明被标记注释可以对同个注释或类型使用(type use)多次应用,更多内容请看Repeating Annotations

类型注释(Type Annotations)和可插入类型系统(Pluggable Type Systems)

在Java SE 8 发布之前,注释只能被用于声明。自Java SE 8发布之后,注释可以被用于任何类型使用(type use)。这意味着使用类型时就可以使用注释。一些例子比如类实例创建表达式(new),类型转换,implements子句,throws子句。这种形式的Annotation被称为类型注释,几个例子可以在Annotations Basics中查看。

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

例如,您想要确保您程序中的一个特殊变量不会被赋null值;避免触发一个NullPointerException。您可以编写一个自定义插件来检索。然后,您可以给这变量注释:

@NonNull String str;
  • 1

在编译代码时,(包括命令行中的NonNull模组),当探测到一个潜在问题,编译器将打印一个警告,您可以改变代码来避免错误。当您改正代码以去掉所有警告,程序运行时不再出现特定错误。

您可以使用多重类型检查模组,每个模组检查不同类型的错误。通过这种方法,您可以在Java类型系统之上进行构建,当您需要时添加特定的类型检查。

通过明智地使用类型注释和插入式类型检查,您的代码将更健壮,更少出错。

在很多情况下,您不需要写编写您自己的类型检查模组,有第三方已经帮您写了。例如,您想要利用华盛顿大学的检查器框架,这个框架包括NonNull模组,一个正则表达式(regular expression)模组,一个互斥锁模组。更多信息,请看Checker Framework

重复注释(Repeating Annotations)

有些情况下您想对一个声明或类型使用(type use)重复应用一个注释。Java SE 8推出之后,重复注释机制允许您这样做。

比如,您正在写一个定时器服务使得方法在给定时间或确定时间表运行,类似于UNIX cron服务。现在您想设置定时器来运行一个叫doPeriodicCleanup的方法,在每月的最后一天和每周五晚上11点。为了设置定时器运行,创建了一个@Schedule注释并在doPeriodicCleanup方法两次应用它。第一次使用指定每月最后一天而第二次使用指定每周五晚上11点,如下面代码所展示的:

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

前面的例子对一个方法用一个注释,所有可以使用标准注释的地方都可以使用重复注释。例如,您有一个类用于处理未经许可访问异常,为角色managers使用一个@Alert注释并为角色admins也使用一次:

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

由于复杂的原因,重复注释被存储在一个Java编译器自动生成的容器注释。为了让编译器这样做,您的代码中需要有两个声明。

第一步:声明一个重复注释类型

该注释类型必须被@Repeatable诠释注释(meta-annotation)标记。以下例子自定义了一个可重复注释类型:

import java.lang.annotation.Repeatable;

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

@Repeatable诠释注释括号中的值,是由Java编译器生成以存储重复注释的容器注释的类型。在这个例子中,存储注释类型是Schedules,所以重复的Schedule注释被存储在Schedules注释中。

未声明注释是可重复的但重复使用注释将导致编译时错误。

第二步:声明容器注释(Containing Annotation)类型

容器注释类型必须有名为value元素的数组类型。数组类型的成员必须是可重复注释类型。声明方式如下:

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

检索注释(Retrieving Annotations)

反射API(Reflection API)中有几个可用于检索注释的方法。返回单个注释(如AnnotatedElement.getAnnotation(Class))的方法的行为没有改变,因为它们只在存在请求类型的注释时返回单个注释。如果请求类型存在一个以上的注释,您可以首先获取它的容器注释。这种情况下老代码是可用的。Java SE 8中介绍的其他方法可扫描容器注释以一次性返回多重注释,如 AnnotatedElement.getAnnotationsByType(Class)AnnotatedElement类规范中有所有可用方法的信息。

设计准则

当设计一个注释类型时,您必须考虑类型使用次数。现在您可以使用一个注释0次,1次,或多于1次(当注释类型被标记@Repeatable)。用@Target诠释注释也是可行的。例如,您可以创建一个仅可用于方法和变量的可重复注释。尽可能小心设计您的注释类型以确保使用者可以灵活而有力地使用它。

问题与练习:Annotations

问题

  1. 以下接口哪里错了
public interface House {
    @Deprecated
    void open();
    void openFrontDoor();
    void openBackDoor();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  1. 考虑以下对问题1中House接口的实现。
public class MyHouse implements House {
    public void open() {}
    public void openFrontDoor() {}
    public void openBackDoor() {}
}
  • 1
  • 2
  • 3
  • 4
  • 5

如果您编译这段代码,编译器将产生一个警告,因为open方法被弃用,如何避免这个警告?

  1. 以下代码会不会编译错误?为什么?
public @interface Meal { ... }

@Meal("breakfast", mainDish="cereal")
@Meal("lunch", mainDish="pizza")
@Meal("dinner", mainDish="salad")
public void evaluateDiet() { ... }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

练习

  1. 用id、synopsis、engineer、date元素定义定义一个注释类型以处理增强请求。指定engineer的默认值为unassigned,date的默认值为unknown。

检查您的答案。

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号