为什么要引入注解?
在没有注解前,在描述程序中的元数据(配置参数,函数信息等)时大多通过 XML 文件的方式实现。XML 文件将元数据描述和代码完全解耦,当所需描述的元数据过多时,程序出错时难定位,维护起来很困难。
注解则将代码和元数据耦合到了一起,程序出错时定位方便,维护起来也很方便,只要找到其对应的注解去配置即可。
借用 Stack Overflow 上的一个例子 来理解,如果我们想要介绍一个类,可以在类的头部添加一些描述信息(作者,版本,上次修改日期等):
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();
}
然后把这个注解放到所需描述的类的头部:
@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
}
以后想要获取这个类的相关描述信息,通过反射获取相关参数即可,后续维护时也可以直接顺手修改,非常方便。
定义注解
定义注解和定义一个类非常相似,只需要通过@interface
来声明即可:
MyAnnotation.java
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
public @interface MyAnnotation {
String myAnnotationValue();
}
其中@Retention
指明了该注解如何保存,即RetentionPolicy
:
- SOURCE – 在
*.java
文件中保留; - CLASS – 在编译得到的
*.class
文件中保留; - RUNTIME – 在程序运行时可被 JVM 获取,通过反射可以获取。
@Target
则指明了该注解可以在源代码的哪些地方使用:
- TYPE – 类、接口(包括自定义的注解)或者枚举类的声明位置,即源代码的声明首部;
- FIELD – 字段或者枚举类中的常量;
- METHOD – 方法(函数)的声明位置;
- PARAMETER – 方法(函数)的参数位置;
- CONSTRUCTOR – 构造方法;
- LOCAL_VARIABLE – 局部变量;
- ANNOTATION_TYPE – 所声明的注解上方,即
注解
可以用来注解注解
,实际上@Retention
和@Target
也是注解,我们上面自定义注解的时候也用到了它们; - PACKAGE – 包;
- TYPE_PARAMETER – 类型参数声明(1.8 新加入),任意使用类型的地方;
- TYPE_USE – 类型使用声明(1.8 新加入),任意声明类型的地方。
诸如@Retention
、@Target
这类注解属于元注解,用于注解其它的注解,它们的@Target
的值均为ANNOTATION_TYPE
。
Java 中内置了四种元注解,除了上述的@Retention
和@Target
,还包括:
@Documented
– 标记这些注解是否包含在用户文档中;@Inherited
– 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)。
除此之外还有三个特殊的注解:
@Override
– 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误;@Deprecated
– 标记过时方法。如果使用该方法,会报编译警告;@SuppressWarnings
– 指示编译器去忽略注解中声明的警告。
获取注解
通过 Java 的反射特性可以获取注解的相关信息,比如注解类型、注解的参数值等。
AnnotationTest.java
@MyAnnotation(myAnnotationValue = "TYPE")
public class AnnotationTest {
@MyAnnotation(myAnnotationValue = "HELLO")
public int number = 123;
@MyAnnotation(myAnnotationValue = "WORLD")
public String str = "hola";
}
Main.java
public class Main {
public static void main(String[] args) {
Class<?> cls = AnnotationTest.class;
// 获取字段中的注解
for (Field field : cls.getFields()) {
for (Annotation annotation : field.getAnnotations()) {
if (field.isAnnotationPresent(MyAnnotation.class)) {
MyAnnotation myAnnotation = field.getAnnotation(MyAnnotation.class);
System.out.println(myAnnotation.myAnnotationValue());
}
}
}
// 获取类或接口上的注解
if (cls.isAnnotationPresent(MyAnnotation.class)) {
MyAnnotation annotation = cls.getAnnotation(MyAnnotation.class);
System.out.println(annotation.myAnnotationValue());
}
}
}
输出
HELLO
WORLD
TYPE