注解
注解(Annotation)是放在Java源码的类、方法、字段、参数前的一种特殊“注释”。
注释会被编译器直接忽略,注解则可以被编译器打包进入class文件,因此,注解是一种用作标注的“元数据”。
注解的作用
- 注解本身对代码逻辑没有任何影响,如何使用注解完全由工具决定。
自定义注解
定义格式
public @interface 注解名 {
public 类型 属性名() [default 默认值];
}
使用格式
@注解名(属性名1 = 值1, 属性名2 = 值2, ...)
特殊属性名 value
- 只有一个value属性时,使用注解时可以省略value属性名,直接赋值。
- 有包括value多个属性时,其他属性有默认值,可以省略value属性名,直接赋值。
- 有包括value多个属性时,其他属性没有默认值,不能省略。
代码演示
// 注解类
public @interface HelloWorld {
public String value();
}
@HelloWorld("aaa")
public class AnnotationDemo {
public static void main(String[] args) {
@HelloWorld("aaa") int num = 99;
add1(num);
}
@HelloWorld("aaa")
public static int add1(int num) {
return num + 1;
}
}
元注解
元注解就是注解注解的注解,主要用于注解自定义注解。
元注解 | 说明 |
---|---|
@Target | 约束自定义注解可注解的位置 |
@Retention | 声明注解的生命周期 |
在ElementType
枚举类中可限制注解的位置,如下:
可修饰 | |
---|---|
ElementType.TYPE | 类、接口或枚举类型 |
ElementType.FIELD | 成员变量 |
ElementType.METHOD | 方法 |
ElementType.PARAMETER | 方法参数 |
ElementType.CONSTRUCTOR | 构造器 |
ElementType.LOCAL_VARIABLE | 局部变量 |
ElementType.ANNOTATION_TYPE | 注解 |
ElementType.PACKAGE | 包 |
在RetentionPolicy
枚举类中,可声明注解的生命周期,如下:
说明 | |
---|---|
SOURCE | 只作用于源码阶段 |
CLASS | 作用于源码阶段、字节码文件阶段 |
RUNTIME | 作用于源码阶段、字节码文件阶段、运行阶段(常用) |
SOURCE
类型的注解主要由编译器使用,因此我们一般只使用,不编写。CLASS
类型的注解主要由底层工具库使用,涉及到class的加载,我们很少用到。RUNTIME
类型的注解经常使用。
在单元测试框架JUnit 中 @Test
源码如下:
// 注解作用于源码阶段、字节码文件阶段、运行阶段
@Retention(RetentionPolicy.RUNTIME)
// 注解只能作用于 方法
@Target({ElementType.METHOD})
public @interface Test {
...
}
解析注解
注解定义后也是一种class
,所有的注解都继承自java.lang.annotation.Annotation
,因此读取注解需要使用反射。
Java提供的使用反射API读取Annotation
的方法:
Class Field Method Constructor API | 说明 |
---|---|
boolean isAnnotationPresent (Class annotation) | 判断某个注解是否作用于Class 、Field 、Method 或Constructor |
A getAnnotation (Class annotationClass) | 获取指定注解 |
Annotation[] getAnnotations () | 获取所有注解 |
注解类:
//仅作用于类、方法
@Target({ElementType.TYPE, ElementType.METHOD})
//作用于源码阶段、字节码文件阶段、运行阶段
@Retention(RetentionPolicy.RUNTIME)
public @interface Book {
public String value(); //书名
public double price() default 29.9; //价格,默认29.9
public String[] author(); //作者
}
@Book(value = "《活着》", author = {"余华"})
public class BookStore {
@Book(value = "《XXX》", price = 46.9, author = {"yyy", "zzz"})
public void book() {
}
}
思路一: 判断Annotation
是否存在,如果存在,再获取
public static void main(String[] args) {
//1.获取类对象
Class cl = BookStore.class;
//2.判断Book注解是否作用在类
if (cl.isAnnotationPresent(Book.class)) {
//3.获取注解
Book clAnnotation = (Book) cl.getAnnotation(Book.class);
...
}
}
思路二: 是直接读取Annotation
,如果Annotation
不存在,将返回null
:
public static void main(String[] args) throws NoSuchMethodException {
//1.获取方法对象
Method m = cl.getDeclaredMethod("book");
//2.获取注解
Book mb = m.getAnnotation(Book.class);
//3.判断注解是否存在
if (mb != null) {
...
}
}
读取字段和构造方法的Annotation
与上述类似。
读取方法参数的Annotation
就比较麻烦一点,因为方法参数本身可以看成一个数组,而每个参数又可以定义多个注解,所以,一次获取方法参数的所有注解就必须用一个二维数组来表示,如下:
//1.获取方法对象
Method mt = cl.getDeclaredMethod("test", int.class, String.class, int.class);
//2.获取所有参数的二维数组
Annotation[][] annotationss = mt.getParameterAnnotations();
//3.遍历数组
for (Annotation[] annotations : annotationss) {
for (Annotation annotation : annotations) {
if (annotation instanceof This) {
// annotation是@This注解对象
This t = (This) annotation;
...
}
if (annotation instanceof Null) {
// annotation是@Null注解对象
Null n = (Null) annotation;
...
}
}
}
使用注解
注解如何使用,完全由程序自己决定。如测试框架JUnit,它会自动运行所有标记为@Test
的方法。
案例: 字段的大小满足@Range
的参数定义
注释类:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Range {
public int min() default 0; //下限
public int max() default 80; //上限
}
JavaBean:
public class Person {
@Range
public int age; //使用注解限制 age
...
}
编写一个Person
实例的检查方法check,检查Person
实例的字段长度是否满足@Range
的限制:
public static void check(Person person) throws IllegalAccessException {
//遍历所有字段
for (Field field : person.getClass().getDeclaredFields()) {
//获取field定义的@Range
Range range = field.getAnnotation(Range.class);
//如果@Range存在
if (range != null) {
//获取使用@Range注解的字段的值
Object obj = field.get(person);
//如果是值是int类型
if (obj instanceof Integer) {
Integer i = (Integer) obj;
//判断值是否在限定范围内
if (i > range.max() || i < range.min()) {
throw new IllegalAccessException("Invalid field: " + field.getName());
}
}
}
}
}