注解是众多引入到Java SE5中的重要的语言变化之一。
它们可以提供用来完整地描述程序所需的信息,而这些信息是无法用Java来表达的
什么是注解(Annotation)?注解是放在Java源码的类、方法、字段、参数前的一种特殊“注释”:
定义一个注解时,还可以定义配置参数。配置参数可以包括:
- 所有基本类型;
- String;
- 枚举类型;
- 基本类型、String、Class以及枚举的数组。
Java语言使用@interface语法来定义注解(Annotation),它的格式如下:
一般核心参数我们都用 value
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
元注解
有一些注解可以修饰其他注解,这些注解就称为元注解(meta annotation)。
Java标准库已经定义了一些元注解,我们只需要使用元注解,通常不需要自己去编写元注解
必须设置@Target和@Retention,@Retention一般设置为RUNTIME,
因为我们自定义的注解通常要求在运行期读取。
@Target
最常用的元注解是@Target。使用@Target可以定义Annotation能够被应用于源码的哪些位置:
- 类或接口:ElementType.TYPE;
- 字段:ElementType.FIELD;
- 方法:ElementType.METHOD;
- 构造方法:ElementType.CONSTRUCTOR;
- 方法参数:ElementType.PARAMETER。
- 定义注解@xxx可用在方法或字段上,可以把@Target注解参数变为数组{ ElementType.METHOD, ElementType.FIELD }:
@Retention
另一个重要的元注解@Retention定义了Annotation的生命周期:
- 仅编译期:RetentionPolicy.SOURCE 注解在编译期就被丢掉了
- 仅class文件:RetentionPolicy.CLASS 注解仅保存在class文件中,它们不会被加载进JVM
- 运行期:RetentionPolicy.RUNTIME 注解会被加载进JVM,并且在运行期可以被程序读取
如果@Retention不存在,则该Annotation默认为CLASS。因为通常我们自定义的Annotation都是RUNTIME,所以,务必要加上@Retention(RetentionPolicy.RUNTIME)这个元注解:
处理注解
因为注解定义后也是一种class,所有的注解都继承自java.lang.annotation.Annotation,
因此,读取注解,需要使用反射API。
判断某个注解是否存在于Class、Field、Method或Constructor:
- Class.isAnnotationPresent(Class)
- Field.isAnnotationPresent(Class)
- Method.isAnnotationPresent(Class)
- Constructor.isAnnotationPresent(Class)
// 判断@Report是否存在于Person类:
Person.class.isAnnotationPresent(Report.class);
使用反射API读取Annotation:
- Class.getAnnotation(Class)
- Field.getAnnotation(Class)
- Method.getAnnotation(Class)
- Constructor.getAnnotation(Class)
// 获取Person定义的@Report注解:
Report report = Person.class.getAnnotation(Report.class);
int type = report.type();
String level = report.level();
利用反射直接读取Annotation,如果Annotation不存在,将返回null:
Class cls = Person.class;
Report report = cls.getAnnotation(Report.class);
if (report != null) {
...
}
使用注解
注解如何使用,完全由程序自己决定。例如,JUnit是一个测试框架,它会自动运行所有标记为@Test的方法。
我们来看一个@Range注解,我们希望用它来定义一个String字段的规则:字段长度满足@Range的参数定义:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Range {
int min() default 0;
int max() default 255;
}
public class Person {
@Range(min = 1,max = 3)
public String name;
@Range(max = 4)
public String city;
void checkName(Person person) throws Exception {
//通过反射获取实例
final Class<?> fileds = Class.forName("com.java.chapter20.CheckPerson.Person");
//遍历所有字段
for (Field f : fileds.getFields()) {
//获取字段定义的@Range注解
final Range range = f.getAnnotation(Range.class);
//如果注解存在
if (range !=null){
//获取此对象上(person)的值
final Object value = f.get(person);
//转型
if (value instanceof String){
String s =(String)value;
//判断值是否满足注解上的定义
if (s.length()< range.min() || s.length()> range.max()){
throw new IllegalArgumentException("错误字段 " + f.getName());
}
}
}
}
}
}
public class Test {
public static void main(String[] args) throws Exception {
final Person p = new Person();
p.name="sadsa";
p.city="an";
p.checkName(p);
}
}
重复注解
在该版本之前使用注解的第一个限制是相同的注解在同一个位置只能声明一次,不能声明多次。java8引入了重复机制,这样相同的注解可以在同一个地方声明多次。重复注解机制本身必须用@Repeatable注解。
@Retention(RetentionPolicy.RUNTIME) \\该注解存在于类文件中并在运行时可以通过反射获取
@interface Annots {
Annot[] value();
}
@Retention(RetentionPolicy.RUNTIME) \\该注解存在于类文件中并在运行时可以通过反射获取
@Repeatable(Annots.class)
@interface Annot {
String value();
}
@Annot("a1")@Annot("a2")
public class Test {
public static void main(String[] args) {
Annots annots1 = Test.class.getAnnotation(Annots.class);
System.out.println(annots1.value()[0]+","+annots1.value()[1]);
// 输出: @Annot(value=a1),@Annot(value=a2)
Annot[] annots2 = Test.class.getAnnotationsByType(Annot.class);
System.out.println(annots2[0]+","+annots2[1]);
// 输出: @Annot(value=a1),@Annot(value=a2)
}
}
注解Annot 被@Repeatable(Annots.class) 注解。Annots 只是一个容器,它包含Annot数组,编译器尽力向程序员隐藏他的存在。通过这样的方式,Test类可以被Annot注解两次。重复注解的类型可以通过getAnnotationsByType() 方法来返回。