注解
1、注解的作用
- 可以用于编译器发现错误或者抑制异常
- 编译时可以利用注解生成代码,部署时可以处理XML文件等。
- 可以用于运行时检查
2、基础
一般格式如:@Entity,使用@开头,向编译器表示这是个注解.
注解可以包含参数:
@Author(
name = "Benjamin Franklin",
date = "3/27/2003"
)
class MyClass {...}
或者:
@SupressWarnings(value = "unchecked")
void myMethod() {...}
如果只有一个参数名为value, 则该参数名可以忽略不写。
@SuppressWarnings("unchecked")
void myMethod() { ... }
如果注解不包含参数,括号也可以忽略不写,而且多个注解可以叠加。
@Author(name = "Jane Doe")
@EBook
class MyClass { ... }
也可以同时存在多个相同类型的注解:
@Author(name = "Jane Doe")
@Author(name = "John Smith")
class MyClass { ... }
重复注解在Java SE 8被支持. 在前面的例子中,Override和SuppressWarnings是预定义注解,详见predefined Java annotations. 其他的如Author和Ebook为自定义注解.
3、注解的使用场景
注解可以应用于各种声明: 类、变量、方法,以及其他编程元素。当使用在声明时,注解一般出现在声明的同一行.
创建类实例
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
4、声明注解类型##
许多注解可以在代码中替换注释。
一般在类开始的地方会加上一些注释,如:
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前面加上 @ 符号。 注解类型是接口的一种形式。
定义完注解类型之后,就可以使用该注解,如:
@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
}
Note: 如果需要 @ClassPreamble 中的信息能出现在Javadoc生成的文档中,需要加上 @Documented 注解.
// import this to use @Documented
import java.lang.annotation.*;
@Documented
@interface ClassPreamble {
// Annotation element definitions
}
4、预定义注解类型
Java SE中定义了一系列注解类型。有些用于Java编译器,有些用于其他的注解。
Java语言中使用的注解类型
在java.lang中定义的注解类型有@Deprecated, @Override 和 @SuppressWarnings.
@Deprecated @Deprecated注解表示所标记的元素是退化的并且在未来不再使用。当在程序中使用@Deprecated注解标记方法、类或者变量时,编译器会产生警告。当一个元素退化时,也应该使用Javadoc的@deprecated标签,如下例所示。在Javadoc和注解中都使用 @ 符号并不是一个巧合: 他们存在概念上的关联。 同时请注意:注解使用大写的D,Javadoc使用小写的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@SuppressWarnings该注解告诉编译器抑制特定警告的产生。 如下例所示,退化函数的使用,编译器会产生警告,在这种情况下,注解会抑制警告的产生。
// 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"})
@SafeVargs@SafeVarargs注解,当应用在方法或构造函数中,会假定对于varargs参数,代码不会执行潜在的不安全的操作,该注解会抑制跟varargs有关的unchecked警告。
FunctionallInteface@FunctionallInteface,出现在Java SE 8中,表示该注解声明的类型是一个函数接口。
应用在注解上的注解
应用在其他注解上的注解被称为 meta-annotations,(元注解?). meta-annotations一般定义在java.lang.annotation.
@Retention@Retention指定被标注的注解如何存储:
RetentionPolicy.SOURCE - 表示被标注的注解只保持在源代码层面,会被编译器忽略。
RetentionPolicy.CLASS - 表示被标注的注解会在编译时被编译器保持,但是会被JVM忽略。
RetentionPolicy.RUNTIME - 表示被标注的注解会被JVM保持,所以可以在运行时使用。
@Documented@Documented表示被标注的注解无论用在什么元素上,该注解都会被Javadoc工具生成在文档中。(默认情况下,注解不会被包括在Javadoc中)。详细说明请参考Javadoc tools page
@Target@Target表示被标注的注解会被限制在那种Java元素上使用。 目标注解类型如下:
ElementType.ANNOTATION_TYPE 表示可以应用在注解类型上。
ElementType.CONSTRUCTOR 表示可以应用在构造函数上。
ElementType.FIELD 表示可以应用在变量或属性上。
ElementType.LOCAL_VARIABLE 表示可以应用在本地变量上。
ElementType.METHOD 表示可以应用在函数上。
ElmentType.PACKAGE 表示可以应用在包声明上。
ElementType.PARAMETER 表示可以应用在方法参数上。
ElementType.TYPE 表示可以应用在类的任何元素上。
Inherited@Inherited 表示该注解类型可以从超类继承。(默认是否)。当用户查找注解类型,但是类中并没有应用该注解,则该类的超类也会被查询。该注解只能应用在类声明上。
@Repeatable@Repeatable在Java SE 8中引入,表示该注解可以在同一个声明或类型上多次使用。详细说明见Repeating Annotations
类型注解和可插拔类型系统
在Java SE 8发布之前,注解只能用于声明。 之后,注解可以用于任何类型使用。这意味着注解可以在你使用类型的任何地方使用。一些简单的例子,如类实例创建(new), casts, implements,以及 throw。 这些形式的注解被称为类型注解。
类型注解支持提高了Java程序的强类型检查的分析能力。Java SE 8并没有提供类型检查框架,但是它允许你实现或者下载类型检查框架,作为一个或多个可插拔模块,用于与Java编译器的连接。
比如,你想确保在你的程序中一个特定的变量永远不会被设为null; 你想避免触发NullPointerException。 你可以写一个自定义插件来检查这个变量。你可以用注解标记该变量,表示它不能被赋值为null。这个变量声明类似于:
@NonNull String str;
当你编译该代码,并在命令行中引入NotNull模块时,如果编译器检查到潜在的错误时,编译器就会打印警告,提醒你修改代码避免错误。当做完修改移除警告时,当程序运行该特定错误就不会发生。
可以使用多个类型检查模块,每个模块检查不同的错误。用这种方式,你可以在Java类型系统之上构建,当你想需要的时候可以使用它们进行特殊类型检查。
在很多情况下,你把需要编写自己的类型检查模块。有许多三方为你做了这些工作。比如,你可以使用华盛顿大学的Checker Framework。该框架包括NotNull模块,以及一些正则表达式模块。详见Checker Framework
5、重复注解##
在一些情况下,你需要在一个声明或类型上应用同一个注解。在Java SE 8发布之后,
reapeating annotations可以让你实现这些功能。
比如,你想使用定时服务以在给定的时间或时间表执行某个函数,类似于UNIX的cron。现在你可以使用一个定时器去执行函数doPeriodicCleanup, 在每个月的最后一天和每个周五的晚上11点执行。为了执行这个定时器,创建一个@Schedure注解,并且在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 { ... }
兼容性考虑,重复注解一般会存储在由编译器生成的容器注解中(container annotation)。 为了让编译器作者,需要加两个声明.
步骤1: 声明一个重复注解类型
注解类型必须被@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元注解的值,是编译器生成的容器注解,用于存储重复注解。在这个例子中,容器注解类型为 Schedules, 所以 @Schedule 注解存储在 @Schedules 中。
步骤2: 声明一个容器注解类型
容器注解类型必须包含一个value属性,并且是数组。 数组的类型必须为可重复的注解类型。声明 Schedules 容器注解类型如下:
public @interface Schedules {
Schedule[] value();
}
查找注解
在反射接口中有许多方法用于查找注解。有个返回单个注解的方法,如 AnnotatedElement.getAnnotation(Class), 如果有一个需要查找的类型存在,就返回单个注解。 如果有多个注解出现,你可以通过容器注解类型间接获取它们。 在这种方式下,老的代码也可以正常工作。 在Java SE 8中也存在其他的方法可以一次返回多个注解,如 AnnotatedElement.getAnnotationsByType(Class).更详细的说明见AnnotatedElement.
设计考虑
当设计一个注解类型时,你必须考虑那个类型的基准注解。 现在可以使用一个注解0次,一次,或者,如果一个注解被标注为 @Repeatable,多次。 你可以使用 @Target 元注解限制该注解类型的使用。比如,你可以创建一个重复注解只能使用再方法和域变量上。 非常重要的一点,你必须尽可能的使注解在使用时使编码更灵活和更强大。