Java基础学习——注解(Annotations)学习

注解(Annotations)是一种元数据,提供了程序之外的一些程序信息。注解并不会直接影响被注解的代码。注解有很多用法:
1. 为编译器提供信息(Information for the compiler) ——编译器能直接使用注解检查错误(detect errors)和禁止警告(suppress warnings)。
2. 编译期和部署时处理(Compile-time and deployment-time processing) —— 软件工具可以使用注解生成代码,XML文件等待。
3. 运行期处理(Runtime processing) —— 一些注解可以在运行期被检测。

这篇教程主要解释了哪些地方应该使用注解,如何应用注解,如何更好使用Java预定义注解类型;注解如何在支持插件的类型系统,写出强类型检测的代码,以及如何使用重复注解(repeating annotations)。

基础知识(Annotations Basics)

注解格式

最简单的注解格式,看起来像下面这样:

@Entity

标识字母(@)告诉编译器,紧跟其后的是个注解。下面的例子里,有个名叫Override的注解:

@Override
void mySuperMethod() { ... }

注解也可以包含元素,元素可以是命名的(named)也可以是未命名的(unnamed),这里有些元素的值:

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

或者

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

如果仅仅只有一个name为value的元素,通常name是可以被省略的。像下面这样:

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

如果注解没有元素,则括号也可以被省略,就像上面的@Override例子。

在一个声明上,使用多个注解也是可以的。

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

如果多个注解是同一个类型,则被叫做重复注解(repeating annotation)。

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

重复注解是Java SE 8才开始支持的。后面会有更多详细信息。

注解类型可以是java.lang或 java.lang.annotation包里的一个类型。在上面的例子里,Override 和 SuppressWarnings都是预定义注解( predefined Java annotations)。但我们也可以定义自己的注解类型。上面Author和Ebook便是两个用户自定义注解类型。

注解的使用(Where Annotations Can Be Used)

注解可以使用在任何声明的地方:类,方法,属性和其他一些程序元素声明。当声明被注解时,按照惯例,注解通常独占一行。

在Java 8,注解也可以在类型上使用。这里有一些例子:
1. new 对象的表达式(Class instance creation expression):

new @Interned MyObject();
  1. 类型转换(Type cast)
myString = (@NonNull String) str;
  1. implements 子句(implements clause)
class UnmodifiableList<T> implements
        @Readonly List<@Readonly T> { ... }
  1. 异常抛出声明(Thrown exception declaration)
void monitorTemperature() throws
        @Critical TemperatureException { ... }

上面的这些叫做类型注解。更多详情,请参见Type Annotations and Pluggable Type Systems

以上内容翻译自Java官网 注解教程

声明注解(Declaring an Annotation Type)

代码中,注解可以代替很多注释。

假设有个项目,每次开始类的定义时,都需要提供关于类信息的注释:

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

}

为了使用注解来添加这些元数据(metadata),我们必须先定义注解类型。语法如下:

@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前面多了一个@。注解类型是接口(interface)的一种形式,具体内容下面会介绍。现在,我们先不用关注这些。

在上面的注解定义里,包含了一些看起来像方法(method)的注解元素(annotation type element)的定义。注意一下,他们都可以可选地定义默认值。

在注解类型被定义之后,我们可以用真实值填充相关元素,然后使用它了。

@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自动生成的文档里,我们在@ClassPreamble定义时,使用注解@Documented:

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

@Documented
@interface ClassPreamble {

   // Annotation element definitions

}

以上内容翻译自Java官网 declaring

内置预定义注解(Predefined Annotation Types)

Java SE API里内置了很多注解。一些注解是为了编译器使用的,还有一些是为了给其他注解使用的。

Java编译器使用的注解(Annotation Types Used by the Java Language)

在java.lang里有@Deprecated, @Override 和 @SuppressWarnings,都属于这一类型。

@Deprecated

@Deprecated注解表示被标记的元素时被废弃的(deprecated),不应该再被使用。当代码使用被@Deprecated注解的方法,属性或者类的时候,编译器会给出警告(warning)。当一个元素被废弃了,它将会在Javadoc文档里打上@deprecated标记(@deprecated tag),就像下面的例子一样。Javadoc注释和注解都使用@开头,并不是巧合,他们在概念上是相关联的。当然,我们也会看到Javadoc tag是以小写字母d开头,而注解是以大写字母D开头。

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

@Override

@Override 注解告诉编译器被注解的元素时重写(override)父类的。关于重写方法的信息,可以参见接口与继承(Interfaces and Inheritance)

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

重写方法,并不是强制需要这个注解,但这个注解可以帮助避免错误。如果一个被@Override标记的方法,没有正确重写父类的方法,编译器会报错。

@SuppressWarnings

@SuppressWarnings注解告诉编译器不要产生某个的警告。在下面的例子里,使用了一个被废弃(deprecated)的方法,然后编译器会产生一个警告(warning)。在这种情况下,这个注解要会抑制编译器产生警告。

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

每个编译期警告都有一个类型。Java语言规范列出了两种类型:deprecation 和unchecked。当接口是使用泛型前的遗留代码时,就会产生unchecked警告。为了抑制多个类型的警告,可以使用下面语法:

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

@SafeVarargs @SafeVarargs注解应用于一个方法或者构造函数,表示代码不会在可变参数(varargs)上进行不安全的操作。当@SafeVarargs注解被使用时,与可变参数相关(varargs)的unchecked警告都会被抑制。

@FunctionalInterface @FunctionalInterface注解是Java 8新加入的一个注解,用于表示类型是打算用于函数式接口的。

用于其他注解的注解(Annotations That Apply to Other Annotations)

注解用于其他的注解被叫做元注解(meta-annotations)。在java.lang.annotation里定义了很多元注解。

@Retention

@Retention注解指明被标记的注解如何存储:
1. RetentionPolicy.SOURCE 表示被标记的注解,仅仅只在代码级被保留,同时会被编译器忽略;
2. RetentionPolicy.CLASS 该标记表示被标记的注解会被编译器保留,但仅仅在编译期保留,但是会被JVM忽略;
3. RetentionPolicy.RUNTIME 该标记的注解,表示会被JVM保留,可以在运行时被使用。

@Documented

@Documented注解指明被注解的元素,会被Javadoc工具生成到Javadoc文档里(默认,注解是不会包含在Javadoc里的)。更多信息,可以参见Javadoc工具页。

@Target

@Target注解用于注解可以用于哪些元素。一个target注解可以指定下面元素类型值的一种:
1. ElementType.ANNOTATION_TYPE 应用于一个注解类型
2. ElementType.CONSTRUCTOR 应用于构造函数
3. ElementType.FIELD 应用于类的属性
4. ElementType.LOCAL_VARIABLE 应用于局部变量
5. ElementType.METHOD 应用于方法
6. ElementType.PACKAGE 应用于包
7. ElementType.PARAMETER 应用于方法的参数
8. ElementType.TYPE 应用于类的元素

@Inherited

@Inherited注解表示这个注解可以被从父类继承(默认是不可以的)。当用户判断类是否被注解时,同时类并没有直接被注解,这时会去查询父类是否被注解。@Inherited注解只能在class上使用。

@Repeatable

@Repeatable注解是被Java 8新引入的,表示注解可以在同一个声明或类型上使用多次。

以上内容翻译自Java 官网教程 内置注解

Type注解和插件(Pluggable Type Systems)

在Java 8之前,注解仅仅只能在声明(declarations)时使用。在Java 8,注解可以在任何 type use。这就是说,注解可以应用在任何你使用type的地方。例如,class实例的创建表达式(new),类型转换(casts),implements子句,throws子句。这种注解被称为type annotation。更多例子,可以参考上面的基础知识(Annotations Basics)。

Type annotation是为了提高Java程序的强类型检查功能而诞生的。Java 8并没有提供一个类型检查框架(type checking framework),但却允许你自己写(或者down)一个类型检查框架作为Java编译器的插件使用。

举个栗子,你想确保你程序里的某些变量永远不会被赋值为null;你想避免产生NullPointerException。你可以自己写个插件来检查。你只需要修改你的代码,把特定变量加上标识不能用null赋值的注解。这个变量的声明可能看起来像这样:

@NonNull String str;

当你使用NonNull模块编译这个代码时,编译器会在会检测到潜在问题时,产生一个异常,从而提醒你修改代码而避免这个错误。在你改正代码清除所有警告后,这个特定的错误便不会在程序运行时产生。

你也可以使用多个检查不同种类错误的类型检查(type-checking)模块。在这种方式下,你可以在任何时候在Java类型系统(Java type system)的任何地方添加特别的检测。

恰当的使用类型注解和类型检查插件,我们可以写出更健壮和不易出错的代码。

在很多情况下,我们都不用自己写类型检查模块。很多第三方都已经为我们写好了这些。例如,你可以使用华盛顿大学(University of Washington)的the Checker Framework。这个框架包含了NonNull模块,同时还有正则表达式检测模块,互斥锁模块。更多信息,可以参见the Checker Framework的官网

以上内容翻译自Java 官网

重复注解(Repeating Annotations)

很多时候,我们想在一个声明或者类型上多次使用同一个注解。在Java 8中,重复注解(Repeating Annotations)便提供了我们需要的这种功能。

比如,我们编写一个通过使用类似unix cron的定时器服务调度的方法。现在我们可以设置一个定时器来运行方法,doPeriodicCleanup,在每月月末和每个周五晚上的23:00时刻运行。为了设置定时器,可以创建一个@Schedule注解,并且在doPeriodicCleanup方法上使用两次。第一次表示每月月末,第二次表示每周五晚上的23:00。像下面这样:

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

上面的例子,是对方法进行注解。你可以对任何可以使用注解的地方进行重复注解。例如,我们有个用于处理未登陆访问异常的class。我们可以使用@Alert注解来表示类的超级管理员和普通管理员。

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

由于兼容性的原因,重复注解(repeating annotations)存储在一个Java编译器自动生成的注解容器(container annotation)里。为了编译器能做这些事情,在我们的代码里需要进行下面两步。

步骤一:定义一个重复注解类型(Declare a Repeatable Annotation Type)

注解类型必须被元注解@Repeatable所标记。下面是个自定义重复注解类型@Schedule的定义:

import java.lang.annotation.Repeatable;

@Repeatable(Schedules.class)
public @interface Schedule {
  String dayOfMonth() default "first";
  String dayOfWeek() default "Mon";
  int hour() default 12;
}

元注解@Repeatable括号内的值,便是Java编译器自动生成的用于存储重复注解(repeating annotations)的注解容器(container annotation)的类型。在这个例子里,这注解容器的类型便是Schedules。

任何没有进行步骤一的注解,如果在同个地方使用两次,都会产生一个编译错误。

步骤二:定义注解容器类型(Declare the Containing Annotation Type)

注解容器类型必须拥有一个返回数组的value方法。Schedules类型注解容器的声明:

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

Retrieving Annotations

在Java反射(Reflection)的API有很多方法用来获取注解信息。有些方法返回一个注解,例如:AnnotatedElement.getAnnotationByType(Class),如果提供的类型拥有注解,则会原封不动地返回。如果一个元素有多个注解,则可以通过注解容器获得。在这种方式下,之前的遗留代码仍然可以兼容。另外一些方法是Java SE 8新添加的,可以一次性返回多个注解,例如 AnnotatedElement.getAnnotations(Class)。更多信息可以参见AnnotatedElement类

注意事项(Design Considerations)

当设计一个注解时,我们必须考虑注解的使用次数。现在注解,可以使用零次,一次或者通过@Repeatable来实现多次使用。我们也可以使用@Target来实现在哪里注解使用的限制。例如,我们可以创建一个重复注解,只能使用在方法和属性上。我们必须非常小心的设计自定义注解,尽可能使注解灵活和强大。

以上内容翻译自Java 官网Repeating Annotations

问题和练习题(Questions and Exercises)

问题一

下面接口有什么问题呢?

public interface House {
    @Deprecated
    void open();
    void openFrontDoor();
    void openBackDoor();
}

问题二

假设MyHouse类implements上面问题一的House接口

public class MyHouse implements House {
    public void open() {}
    public void openFrontDoor() {}
    public void openBackDoor() {}
}

编译这个代码会有一个警告,因为open是deprecated的,如果才能避免这个警告呢

问题三

下面的代码能编译通过吗?原因是什么?

public @interface Meal { ... }

@Meal("breakfast", mainDish="cereal")
@Meal("lunch", mainDish="pizza")
@Meal("dinner", mainDish="salad")
public void evaluateDiet() { ... }

问题四

定义一个拥有四个元素id, synopsis, engineer, 和date的注解;同时,指定engineer的默认值为unassigned,date的默认值是unknown。

答案一

上面的open函数应该指出为啥open函数被deprecated和使用什么函数来代替它,例如:

public interface House { 
    /**
     * @deprecated use of open 
     * is discouraged, use
     * openFrontDoor or 
     * openBackDoor instead.
     * open 函数已经Deprecated,
     * 使用openFrontDoor或
     * openBackDoor来代替它
     */
    @Deprecated
    public void open(); 
    public void openFrontDoor();
    public void openBackDoor();
}

答案二

有两种方式,第一种,添加@Deprecated注解:

public class MyHouse implements House { 
    // The documentation is 
    // inherited from the interface.
    @Deprecated
    public void open() {} 
    public void openFrontDoor() {}
    public void openBackDoor() {}
}

另外,也可以使用@SuppressWarnings 注解:

public class MyHouse implements House { 
    @SuppressWarnings("deprecation")
    public void open() {} 
    public void openFrontDoor() {}
    public void openBackDoor() {}
}

答案三

代码是编译不成功的。在JDK 8之前,重复注解是不支持的。在JDK 8之后,代码还是编译不成功。因为Meal注解没有定义为可重复的(repeatable)。可以修改Meal的定义

@java.lang.annotation.Repeatable(MealContainer.class)
public @interface Meal { ... }

public @interface MealContainer {
    Meal[] value();
}

答案四

代码如下:

/**
 * Describes the Request-for-Enhancement (RFE) annotation type.
 */
public @interface RequestForEnhancement {
    int id();
    String synopsis();
    String engineer() default "[unassigned]";
    String date() default "[unknown]";
}

以上内容翻译自Java 官网Questions and Exercises: Annotations

你可能感兴趣的:(java)