JavaAndroid进阶之路Gradle

夯实基础:Java的注解

2020-03-05  本文已影响0人  肖邦kaka

前言

本文是系列文章的第二篇,Java的注解。个人建议先读完第一篇夯实基础:Java的反射,因为在本文的后半部分,将使用到一些反射的技术,学完了反射再学本文的内容更有助于你理解注解,当然,你不学或者不会反射,也不会对你学习本文的内容造成太大影响,希望大家结合自身的情况进行选择。

注解的概念

首先注解不是注释。注释大家都知道是给我们开发者看的,而注解呢是给程序看的。我们可以把注解理解为标签,这些标签可以用在Java的类、成员变量、成员方法、构造方法、形参、局部变量等等程序属性上面,并且能够在Java文件、编译期和运行时被读取,开发者可以在程序逻辑不被修改的情况下对代码嵌入补充信息。

Java内置的注解

java给我们内置提供了几个注解,下面我们分别看一下

元注解

想了解注解之前,必须要知道什么是元注解。所谓元注解就是注解的注解,本身就是一个注解,用来修饰注解的,先来认识几个java内置的元注解

@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();
}

里面有一个value属性,返回的是ElementType[],看一下ElementType的取值

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
}

取值基本都是包、类、成员方法、成员变量、构造方法、局部变量等等的程序属性

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

里面有一个属性value,返回对象是RetentionPolicy,这是个枚举类,里面有三个枚举值,SOURCE,CLASS,RUNTIME
SOURCE:java源代码阶段
CLASS:把java文件编译成class文件阶段
RUNTIME:程序运行时,基本就等于一直存在,我们绝大多数的时候都用这个阶段

注解的本质

介绍完了元注解,我们现在来了解一下注解的本质是什么。我这里先创建了一个注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TestAnno {
}

通过javac编译生成TestAnno.class,然后再用javap反编译一下TestAnno.class

反编译注解.png
直接看图,我们发现我们创建的TestAnno实际上一个继承了Annotation的接口,Annotion也是一个接口,它是所有注解的父类,到现在我们弄明白了注解的本质就是一个接口。
既然注解是一个接口,那我们就可以用对待接口的方式对待注解,接口里面有抽象方法,我们就可以在注解里面创建抽象方法
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TestAnno {
     String name();
     int age();
}

使用一下这个注解

@TestAnno(name = "张三",age = 22)
public class Person extends Object {

用使用前可以看出,我们创建的是抽象方法,但是在实际用的时候好像跟属性一样,都是XX属性=xxx,其实注解里面的抽象方法就是来描述这个注解的属性的,所以我们在给方法命名的时候最好也按照属性命名。我们如果要使用的注解的话需要给里面的属性赋值,像“@TestAnno(name = "张三",age = 22)”这种,如果实在不想赋值的话,可以在创建属性的时候给默认值

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TestAnno {
     String name() default "张三";
     int age() default 22;
}

所有的注解还有一个默认的属性value,当你使用了value属性,在赋值的时候可以不写前面“value=”

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TestAnno {
    String name() default "张三";
    int age() default 22;
    String value();
}

@TestAnno("555")//这里的555是给value赋值
public class Person extends Object {

注意:注解里面的抽象方法(属性)的返回值,只能是:基本数据类型、String类型、注解类型以及它们的数组,不能是自定义的对象类型以及void,比如

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TestAnno {
    Person person();//编译器就直接报错了
   void test();//不被允许
}

自定义注解

了解了注解的本质以后,我们来自定义一个注解。我们知道修饰类的关键字是class,接口的是interface,枚举的是enum,而修饰注解的就是@interface
注解还必须有@Target和@Retention,举个例子

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
public @interface TestAnno2  {
    String value();
    int[] ids();
    TestAnno anno();
    
}

其实了解了注解的本质以后,自己写个注解根本不是事,然而自定义注解根本不是关键,因为现在这个注解其实没有任何意义。所以我们要解析这些注解,如果解析呢?这里就用到我们上一篇文章学到的反射了。

利用反射解析注解

首先,解释一下为什么要通过反射来解析注解。注解是作用在包、类、变量、方法等程序属性上,如果我们要想拿到注解,就必须先得拿到这些程序属性,而如何能拿到这些程序属性呢?正是通过反射!
下面我将以一个具体的例子来讲解一下

/**
 * 加减乘除
 * */
public class MathCalculation {

    @CheckMath
    public int add(int a,int b) {
        return a+b;
    }

    @CheckMath
    public int sub(int a,int b) {
        return a-b;
    }

    @CheckMath
    public int mul(int a,int b) {
        return a*b;
    }

    @CheckMath
    public int exc(int a,int b) {
        return a/b;
    }
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CheckMath {//用来检查加减乘除四个运算
    int[] aList() default {1,2,3,4,5,6,7,8,9,0};//默认值1~9
    int[] bList() default {1,2,3,4,5,6,7,8,9,0};
}

上面两个代码很简单,我现在需要用CheckMath检查一下加减乘除这四个方法在a和b分别是1~9的情况下是否正确

public class Test {

    public static void main(String[] args)  throws Exception{
        Class<MathCalculation> mathCalculationClass = MathCalculation.class;//拿到MathCalculation class类对象
        MathCalculation mathCalculation = mathCalculationClass.newInstance();
        Method[] methods = mathCalculationClass.getMethods();//获取MathCalculation所有的public方法
        for (Method method:methods) {//遍历所有的public方法
            if (method.isAnnotationPresent(CheckMath.class)) {//判断该方法是否有CheckMath.class
                CheckMath checkMath = method.getAnnotation(CheckMath.class);//获取CheckMath注解对象
                int[] aList = checkMath.aList();//获取a的数组
                int[] bList = checkMath.bList();//获取b的数组
                for (int a:aList) {
                    for (int b:bList) {
                        try {
                            method.invoke(mathCalculation,a,b);//调用计算方法
                        }catch (Exception e) {
                            //出错以后打印log
                            System.out.println("出现错误"+"a= "+a+",b="+b+" 错误原因:"+e.getCause());
                        }

                    }
                }
            }
        }
    }
}

基本上每一行都有注释了,这里就不再赘述,看一眼打印结果

运行结果.jpg
当b为0报了数学异常,因此咱们的CheckMath注解还是发挥了它的作用。
注意:这里我们先用反射拿到了程序属性,再通过程序属性拿到了注解。反射拿到程序属性咱们上一节说过,那为什么程序属性就能拿到注解呢?这个其实很简单,我们打开类似Packge、Class、Constructor、Filed、Method这些程序属性类的源码会发现他们都实现了一个叫AnnotatedElement的接口,在这个AnnotatedElement接口里面定义了跟注解相关的方法,核心常用的有三个

总结:

注解是对程序信息的一种补充标记,本质上是一个特殊的接口,接口里面定义的方法实际上是注解的属性。注解单独使用没有任何意义,必须要结合反射来解析。解析的本质是先通过反射拿到程序信息,再通过程序信息拿到注解对象,而程序信息可以拿到注解对象是因为程序信息实现了AnnotatedElement接口。

上一篇 下一篇

猜你喜欢

热点阅读