46.注解

注解

注解(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)判断某个注解是否作用于ClassFieldMethodConstructor
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());
                }
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值