Java 基础 - 注解
# 概述
从 JDK 5.0 开始,Java 增加了对元数据(MetaData) 的支持,也就是注解(Annotation)。注解其实就是代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过使用注解,程序员可以在不改变原有逻辑的情况下,为代码注入新功能。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。注解可以像修饰符一样被使用,可用于修饰包类、构造器、方法、成员变量、参数、局部变量的声明。
注解的使用很简单,只是在类名或方法等代码出增加一个 @ 符号标识,这些注解本身不会做任何事情,它们只是存在于源文件中。编译器将他们置于文件中,并且虚拟机会将它们载入。我们需要一个分析注解的其它工具来实现注解表示的特定任务。
# 标准注解
JDK 1.5 开始自带内置注解,常用的有下面三个:
- @Override
- @Deprecated
- @SuppressWarnings
# @Override
该注解的作用是告诉编译器被修饰的方法是重写的父类的中的相同签名的方法,编译器会对此做出检查,若发现父类中不存在这个方法或是存在的方法签名不同,则会报错。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
2
3
4
# @Deprecated
该注解的作用是告诉编译器被修饰的程序元素已被“废弃”,不再建议用户使用。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
2
3
4
5
# @SuppressWarnings
该注解的作用是告诉编译器忽略指定的警告信息。
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
/**
* The set of warnings that are to be suppressed by the compiler in the
* annotated element. Duplicate names are permitted. The second and
* successive occurrences of a name are ignored. The presence of
* unrecognized warning names is <i>not</i> an error: Compilers must
* ignore any warning names they do not recognize. They are, however,
* free to emit a warning if an annotation contains an unrecognized
* warning name.
*
* <p> The string {@code "unchecked"} is used to suppress
* unchecked warnings. Compiler vendors should document the
* additional warning names they support in conjunction with this
* annotation type. They are encouraged to cooperate to ensure
* that the same names work across multiple compilers.
* @return the set of warnings to be suppressed
*/
String[] value();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
该注解的参数如下:
- all:抑制所有警告。
- boxing:抑制装箱、拆箱操作时候的警告。
- cast:抑制映射相关的警告。
- dep-ann:抑制启用注释的警告。
- deprecation:抑制过期方法警告。
- fallthrough:抑制确在 switch 中缺失 breaks 的警告。
- finally:抑制 finally 模块没有返回的警告。
- hiding:抑制与隐藏变数的区域变数相关的警告。
- incomplete-switch:忽略没有完整的 switch 语句。
- nls:忽略非 nls 格式的字符。
- null:忽略对 null 的操作。
- rawtype:使用 generics 时忽略没有指定相应的类型。
- restriction:抑制与使用不建议或禁止参照相关的警告。
- serial:忽略在 serializable 类中没有声明 serialVersionUID 变量。
- static-access:抑制不正确的静态访问方式警告。
- synthetic-access:抑制子类没有按最优方法访问内部类的警告。
- unchecked:抑制没有进行类型检查操作的警告。
- unqualified-field-access:抑制没有权限访问的域的警告。
- unused:抑制没被使用过的代码的警告。
# 元注解
用于修饰其它注解定义的注解被称为元注解(Meta Annotation)。JDK5 中提供了 4 个标准的元注解:
- @Retention
- @Target
- @Documented
- @Inherited
- @repeatable(Java 8 新增)
- @Native(Java 8 新增)
# @Retention
用于指定该注解的生命周期, @Rentention 包含一个 RetentionPolicy 类型的成员变量, 使用@Rentention 时必须为该 value 成员变量指定值:
RetentionPolicy.SOURCE
:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的注释。RetentionPolicy.CLASS
:在 class 文件中有效(即 class 保留),当运行 Java 程序时, JVM 不会保留注解。这是默认值。RetentionPolicy.RUNTIME
:在运行时有效(即运行时保留),当运行 Java 程序时, JVM 会保留注释。程序可以通过反射获取该注释。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
2
3
4
5
6
7
8
9
10
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# @Target
用于指定被修饰注解能用于修饰哪些元素,取值有:
ElementType.TYPE
:用以修饰类、接口或枚举类。ElementType.FIELD
:用以修饰成员变量。ElementType.METHOD
:用以修饰成员方法。ElementType.PARAMETER
:用以修饰方法参数。ElementType.CONSTRUCTOR
:用以修饰构造方法。ElementType.LOCAL_VARIABLE
:用以修饰局部变量。ElementType.ANNOTATION_TYPE
:用以修饰注解类。ElementType.PACKAGE
:用以修饰包。ElementType.TYPE_PARAMETER
:用以修饰类型参数,JDK 1.8 新增。ElementType.TYPE_USE
:用以修饰使用类型的任何地方,JDK 1.8 新增。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}
2
3
4
5
6
7
8
9
10
11
12
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# @Documented
描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
2
3
4
5
# @Inherited
被它修饰的注解将具有继承性。如果某个类使用了被@Inherited 修饰的注解,则其子类将自动具有该注解。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
2
3
4
5
思考
为什么在定义注解的时候可以使用当前正在定义的这个注解?比如 @Documented 注解,这里感觉有点递归的意思,但是递归至少还是有底线的。
# 自定义注解
注解本质上是一个接口,所有注解都默认继承自java.lang.annotation.Annotation
接口。
- 需要使用
@interface
关键字:
public @interface MyAnnotation {
}
2
- 注解中的成员变量在定义中以无参数方法的形式声明,其方法名和返回值定义了该成员的名字和类型,被称为配置参数:
public @interface MyAnnotation {
String value();
}
2
3
提示
如果只有一个参数成员,建议使用参数名为 value。
- 可以为注解参数成员使用
default
关键字指定初始值:
public @interface MyAnnotation {
String value() default "这是初始值";
}
2
3
- 每个注解可以声明多个成员参数:
public @interface MyAnnotation {
String value() default "这是初始值";
int severity();
}
2
3
4
5
- 成员参数的类型只能是八种基本数据类型(int、short、long、byte、char、double、float、boolean)、String、Class、Enum、Annotation 及各自对应的数组类型。
public @interface MyAnnotation {
enum Status{UNCONFIRMED, CONFIRMED, FIXED, NOTBUG};
boolean showStopper() default false;
String assignedTo() default "[none]";
Class<?> testCase() default Void.class;
Status status() default Status.UNCONFIRMED;
Reference fer() default @Reference;
String[] reportedBy();
}
2
3
4
5
6
7
8
9
- 如果注解中的某个成员参数是数组类型的,那么在使用时应该将它的值用大括号括起来:
@MyAnnotation(reportedBy = {"sqlboy", "baby"})
如果该数组类型的成员参数只有一个值,则可以不用括号:
@MyAnnotation(reportedBy = "sqlboy")
- 由于注解的成员参数可以是另一个注解,那么在使用的时候可以进行嵌套:
@MyAnnotation(ref=@Reference(id="123456"))
# 注解是如何工作的
前面说了那么多关于定义和使用注解的规则及方法,那么注解到底是如何工作的呢?仅仅是定义一个注解类,然后在类或者方法等地方使用该注解就可以完成特定功能吗?显然不是,在前面概述部分说过,我们需要一个分析注解的其它工具来实现注解表示的特定任务,而这个分析注解的其它工具中主要用的就是反射。
注释是给开发人员读的,但是注解是给程序读的。注解要实现特定功能必然有三个阶段:定义注解、使用注解、读取注解。我们前面介绍的内容都是定义注解和使用注解,缺少了读取注解,因为在内置的注解中,Java 已经帮我们完成了这部分。恰恰是完成的读取注解这部分,才是实现注解特定功能的部分。因为定义注解主要是运用在开发工具及框架中,所以工具和框架的开发人员会完成定义注解和读取注解,对于使用工具和框架进行应用开发的人来说,只需要使用注解就可以了。
接下来通过一个自定义注解的案例来理解注解是如何工作的,此案例主要功能是自定义实现 Junit 框架中的 @Test、@Before 和 @After 三个注解。
定义注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyTest {
}
2
3
4
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyBefore {
}
2
3
4
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAfter {
}
2
3
4
使用注解:
class MyService {
@MyBefore
public void before() {
System.out.println("获取数据库连接");
}
@MyTest
public void test() {
System.out.println("处理数据");
}
@MyAfter
public void after() {
System.out.println("释放数据库连接资源");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
读取注解:
public class AnnotationTest {
public static void main(String[] args) throws Exception {
MyAnnotationHandler.handle(MyService.class);
}
}
class MyAnnotationHandler {
public static void handle(Class clazz) throws Exception {
Method[] methods = clazz.getDeclaredMethods();
List<Method> beforeMethodList = new ArrayList<>();
List<Method> testMethodList = new ArrayList<>();
List<Method> afterMethodList = new ArrayList<>();
for (Method method : methods) {
if (method.isAnnotationPresent(MyBefore.class)) {
beforeMethodList.add(method);
}
if (method.isAnnotationPresent(MyTest.class)) {
testMethodList.add(method);
}
if (method.isAnnotationPresent(MyAfter.class)) {
afterMethodList.add(method);
}
}
Object obj = clazz.newInstance();
for (Method testMethod : testMethodList) {
for (Method beforeMethod : beforeMethodList) {
beforeMethod.invoke(obj);
}
testMethod.invoke(obj);
for (Method afterMethod : afterMethodList) {
afterMethod.invoke(obj);
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
输出:
获取数据库连接
处理数据
释放数据库连接资源
2
3