注解(Annotations)是一种元数据,提供了程序之外的一些程序信息。注解并不会直接影响被注解的代码。注解有很多用法:
1. 为编译器提供信息(Information for the compiler) ——编译器能直接使用注解检查错误(detect errors)和禁止警告(suppress warnings)。
2. 编译期和部署时处理(Compile-time and deployment-time processing) —— 软件工具可以使用注解生成代码,XML文件等待。
3. 运行期处理(Runtime processing) —— 一些注解可以在运行期被检测。
这篇教程主要解释了哪些地方应该使用注解,如何应用注解,如何更好使用Java预定义注解类型;注解如何在支持插件的类型系统,写出强类型检测的代码,以及如何使用重复注解(repeating annotations)。
最简单的注解格式,看起来像下面这样:
@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便是两个用户自定义注解类型。
注解可以使用在任何声明的地方:类,方法,属性和其他一些程序元素声明。当声明被注解时,按照惯例,注解通常独占一行。
在Java 8,注解也可以在类型上使用。这里有一些例子:
1. new 对象的表达式(Class instance creation expression):
new @Interned MyObject();
myString = (@NonNull String) str;
class UnmodifiableList<T> implements
@Readonly List<@Readonly T> { ... }
void monitorTemperature() throws
@Critical TemperatureException { ... }
上面的这些叫做类型注解。更多详情,请参见Type Annotations and Pluggable Type Systems
以上内容翻译自Java官网 注解教程
代码中,注解可以代替很多注释。
假设有个项目,每次开始类的定义时,都需要提供关于类信息的注释:
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
Java SE API里内置了很多注解。一些注解是为了编译器使用的,还有一些是为了给其他注解使用的。
在java.lang里有@Deprecated, @Override 和 @SuppressWarnings,都属于这一类型。
@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)父类的。关于重写方法的信息,可以参见接口与继承(Interfaces and Inheritance)
// mark method as a superclass method
// that has been overridden
@Override
int overriddenMethod() { }
重写方法,并不是强制需要这个注解,但这个注解可以帮助避免错误。如果一个被@Override标记的方法,没有正确重写父类的方法,编译器会报错。
@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"})
注解用于其他的注解被叫做元注解(meta-annotations)。在java.lang.annotation里定义了很多元注解。
@Retention注解指明被标记的注解如何存储:
1. RetentionPolicy.SOURCE 表示被标记的注解,仅仅只在代码级被保留,同时会被编译器忽略;
2. RetentionPolicy.CLASS 该标记表示被标记的注解会被编译器保留,但仅仅在编译期保留,但是会被JVM忽略;
3. RetentionPolicy.RUNTIME 该标记的注解,表示会被JVM保留,可以在运行时被使用。
@Documented注解指明被注解的元素,会被Javadoc工具生成到Javadoc文档里(默认,注解是不会包含在Javadoc里的)。更多信息,可以参见Javadoc工具页。
@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注解只能在class上使用。
@Repeatable注解是被Java 8新引入的,表示注解可以在同一个声明或类型上使用多次。
以上内容翻译自Java 官网教程 内置注解
在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 官网
很多时候,我们想在一个声明或者类型上多次使用同一个注解。在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)里。为了编译器能做这些事情,在我们的代码里需要进行下面两步。
注解类型必须被元注解@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。
任何没有进行步骤一的注解,如果在同个地方使用两次,都会产生一个编译错误。
注解容器类型必须拥有一个返回数组的value方法。Schedules类型注解容器的声明:
public @interface Schedules {
Schedule[] value();
}
在Java反射(Reflection)的API有很多方法用来获取注解信息。有些方法返回一个注解,例如:AnnotatedElement.getAnnotationByType(Class
当设计一个注解时,我们必须考虑注解的使用次数。现在注解,可以使用零次,一次或者通过@Repeatable来实现多次使用。我们也可以使用@Target来实现在哪里注解使用的限制。例如,我们可以创建一个重复注解,只能使用在方法和属性上。我们必须非常小心的设计自定义注解,尽可能使注解灵活和强大。
以上内容翻译自Java 官网Repeating Annotations
下面接口有什么问题呢?
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