自定义注解

2024-01-08 05:01:58

一.注解的声明

与声明一个'Class'不同,注解的声明使用@interface关键字。在注解里面可以定义变量,变量可以设置一个默认值,也可以不设置默认值。不设置默认值的时候需要在调用的时候给这个注解的变量塞值。

@Documented
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface Component {
    int priority() default 0;
    int a();
}

二.元注解

元注解就是注解上的注解。

@Target

@Target元注解标明我们的自定义注解是可以注解在类上面,方法上面还是变量上面。

@Retention

@Retention元注解标明我们的自定义注解的保留级别,分别是SOURCE,CLASS,RUNTIME。

如果是SOURCE级别的,只会在源码阶段保留,javac编译成字节码后,自定义注解将会被抹除。

如果是CLASS级别的,即使编译成字节码,自定义注解依旧会在字节码中存在,但是JVM会忽略。

如果是RUNTIME级别的,表示可以在运行阶段让我们拿到这个注解,一般与反射技术一起使用,JVM中不会忽略。

这三个保留级别一层一层递进,后面的包含前面的。

三.注解保留级别的使用场景

根据注解的保留级别不同,对注解的使用自然存在不同场景。由注解的三个不同保留级别可知,注解作用于:源码、字节码与运行时。

SOURCE源码级别

APT技术:Annotation Processor Tools

我们的.java文件会被javac编译成.class文件,javac解析需要编译的java类的时候会采集所有的注解信息,把所有的注解信息包装成一个节点Element,再由javac调用注解处理程序,所以注解处理程序不需要手动调用,它是由javac调用的。

IDE语法检查:

@DrawableRes

android提供的@DrawableRes注解:

我们看到下面的代码,定义了一个setDrawable方法,希望这个方法传入的是一个资源int,但是在实际调用的地方可能会乱传,造成各种问题。

public class Test {
    //这里是想让调用的地方传一个资源的int
    public static void setDrawable( int id){

    }
    public static void main(String[] args) {
        //实际调用乱传
        setDrawable(111);
    }
}

?针对这种情况,我们对setDrawable方法里面入参增加一个@DrawableRes注解:

可以看到,如果在实际调用的地方乱传的话,编译器会给一个提示?“Expected resource of type drawable”。但是实际上还是能编译通过的,只是提示一下而已。

@IntDef注解

android为我们提供了@IntDef注解:

@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.ANNOTATION_CLASS)
public annotation class IntDef(
    /** Defines the allowed constants for this element  */
    vararg val value: Int = [],
    /** Defines whether the constants can be used as a flag, or just as an enum (the default)  */
    val flag: Boolean = false,
    /**
     * Whether any other values are allowed. Normally this is
     * not the case, but this allows you to specify a set of
     * expected constants, which helps code completion in the IDE
     * and documentation generation and so on, but without
     * flagging compilation warnings if other values are specified.
     */
    val open: Boolean = false
)

@IntDef的作用就是提供语法检查的,他是一个元注解,只能在注解上使用此注解:

public class Test {

    //每一个成员就是一个WeekDay对象,耗内存
    enum WeekDay{
        SUNDAY,MONDAY
    }

    private static final int SUNDAY = 0;
    private static final int MONDAY = 1;
    
    public static void setCurrentDay(WeekDay currentDay){
        
    }
    
    //这里是希望传入的值在SUNDAY = 0或者MONDAY = 1里面取,但是实际上调用的地方会乱传值
    public static void setCurrentDay(int currentDay){
        
    }

    public static void main(String[] args) {
        setCurrentDay(SUNDAY);
        //实际上调用的地方乱传值
        setCurrentDay(100);
    }
}

如果一个类里面存在枚举类,那么枚举类里面的每一个成员在生成的.class都会变成一个对象。如果枚举类里面成员非常多,就会生成非常多的对象,会耗费内存。我们可以使用常量代替枚举类。但是如果使用常量的话,真正函数调用的地方传入的值不一定是从0,1中取的,可以传入任何值。这时候就需要用@IntDef了。

我们先自定义一个@WeekDay注解:

public class Test {

    @IntDef({SUNDAY, MONDAY})
    @Target(ElementType.PARAMETER)
    @Retention(RetentionPolicy.SOURCE)
    @interface WeekDay{

    }
    
    private static final int SUNDAY = 0;
    private static final int MONDAY = 1;

    //这里是希望传入的值在SUNDAY = 0或者MONDAY = 1里面取,但是实际上调用的地方会乱传值
    public static void setCurrentDay(@WeekDay int currentDay){}
    
    public static void main(String[] args) {
        setCurrentDay(SUNDAY);
        setCurrentDay(MONDAY);
        setCurrentDay(0);
        setCurrentDay(10);
    }
}

由于@IntDef注解是元注解,需要标注在注解上,所以我们定义一个@WeekDay注解,用@IntDef注解来注解@WeekDay注解,在@IntDef里面标识上允许传的值,然后对@WeekDay标识作用域和保留级。

现在我们可以看到,调用的地方只能传入SUNDAY和MONDAY啦,传入其他的会提示语法不合规。

CLASS字节码级别

用于字节码增强技术:在字节码里面写代码

在编译出Class后,通过修改class数据以实现修改代码逻辑目的。对于是否需要修改的区分或者修改为不同逻辑的判断可以使用注解。

.class文件有自己的格式,里面的数据按照特定的方式记录与排列。如果要直接修改字节码的话,需要把.class文件通过IO操作读成byte[],里面都是16进制的数字,不能乱改,需要按照规定格式来改,太困难太麻烦。

真正的.class文件如下:

可以看到都是16进制的数据,而我们在Android studio里面打开的.class文件都是反编译过的。

我们可以根据方法上是否有@InjectTime注解来决定是否加入特定的额外代码。

RUNTIME运行时级别

反射:在程序运行期间,通过反射技术动态获取注解与其元素,从而完成不同的逻辑判定。

一般情况下,我们使用某个类时必定知首它是什么类,是用来做什么的,并且能够获得此类的引用。于是我们直接对这个类进行实例化,之后使用多个类对象进行操作。
反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。这时候,我们使用 JDK 提供的反射 API 进行反射调用。反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。是Java被视为动态语言的关键。

getField方法:获得自己(不包括private)+父类的成员(不包括private)

getDecleredField方法:只能获得自己的成员(不包括父类)

如果要获取父类的私有变量,通过Class的getSuperclass方法先拿到父类,再调用getDecleredField方法拿到父类私有的变量。

通过getMethod方法获得类的方法的时候,执行method.invoke,第一个参数传递的是对象,第二哥是入参。调用filed的set方法的时候,第一个参数传递的也是对象,第二个传递的是值。

文章来源:https://blog.csdn.net/qq_42426725/article/details/135335004
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。