美文网首页
帮你搞懂java注解(二)

帮你搞懂java注解(二)

作者: _火山_ | 来源:发表于2021-01-05 22:19 被阅读0次

    前言

    承接注解的上一篇帮你搞懂java注解(一),上一篇把注解是什么、有什么用、什么情况下要用都讲了一遍,这篇接着讲解下注解该怎么使用。

    1、java注解有什么分类吗?

    java注解的分类,可以这么分:分为元注解普通注解

    元注解是指用于描述普通注解的注解。

    我们上一篇文章一直在解析注解是用于描述代码元素的元数据信息,当一个注解被定义好之后,它也属于一种代码元素,它也可能有自己的元数据信息,有哪些元数据信息呢:
    例如定义的这个注解能够用在哪些代码元素上;
    这个注解的生命周期能够保留到哪个阶段:源码阶段,还是字节码阶段,或者是程序运行阶段。

    这个注解的这些相关信息需要被记录下来,那通过什么记录呢,就可以通过注解来记录,因为我们都已经知道了注解是可以用来记录代码元素的元数据信息的,但是这里有一点特殊的地方就是:我们要描述的这个代码元素,它恰好也是一个注解,所以我们现在的做法就是通过一个注解来记录另一个注解的元数据信息,那这个用来描述注解的注解,我们就把它称为元注解。

    普通注解是指作用于非注解的代码元素上的注解,如普通注解可以用来记录类、接口、方法、属性、参数的元数据信息,这些代码元素都不是注解。

    所以元注解和普通注解的理解主要是看它的作用对象是什么:
    元注解的作用对象是注解;
    普通注解的作用对象是类、接口、属性、方法等这些代码元素。

    注解还可以这么分类:java内置注解自定义注解

    顾名思义,java内置注解就是发布的java软件包里已经定义好的注解,在开发过程中可以直接拿来使用的注解;
    自定义注解是指由于java内置的注解已经不能满足开发者的需求了,开发者需要自己根据功能需求定义满足要求的注解,java提供自定义注解的功能。

    java有哪些元注解?

    我们可以先找到java.lang.annotation包
    小技巧:在idea中输入代码java.lang.annotation,然后按住ctrl键并点击annotation就可以快速找到jdk中的annotation包

    java元注解

    如上图,我们可以看到annotation包下红框框出的六个注解,它们都是java的元注解,也就是说它们都是用来修饰注解的,分别是:
    @Documented
    @Inherited
    @Repeatable
    @Retention
    @Target

    它们的含义分别如下:
    @Documented
    documented有被记录的意思,用来修饰注解,表示被它修饰过的注解都会被记录下来;
    那记录到哪里去呢,记录到javadoc文档中,成为javadoc文档中api描述的一分子。

    那javadoc文档又是什么呢?
    它是一种java程序的帮助文档,可以用于对整个项目、或者项目的某个模块、或者项目的某个package、或者项目的某个类进行解释,通过查看这个javadoc帮助文档,你可以了解到某个类的继承关系以及它的方法定义,知道方法的参数是什么、返回类型是什么,知道有哪些方法是从父类继承而来的,哪些方法是本类自己定义的。
    如下我定义了一个注解@Flag:可以看到现在这个注解@Flag是没有使用@Documented注解修饰的

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Flag {
        boolean value() default false;
    }
    

    然后我又定义了一个类:在saying()方法上使用注解@Flag

    public class Test {
    
        @Flag(true)
        public void saying(){
            System.out.println("测试注解");
        }
    }
    

    然后我生成了这个类和注解的javadoc帮助文档,如图:

    可以看到这个saying()方法上的描述上是没有任何关于@Flag注解的信息的。

    注:我是通过idea生成的javadoc文档的,关于如何生成javadoc文档我参考自:https://blog.csdn.net/weixin_42140580/article/details/89635775

    下面我修改下@Flag注解,使用@Documented来修饰它

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    @Documented
    public @interface Flag {
        boolean value() default false;
    }
    

    然后再次生成javadoc文档,如图:

    与上次相比,可以看到saying()方法上明显多出了@Flag注解的信息。

    所以,@Documented注解的作用就很明显了:
    在生成javadoc文档时,被@Documented注解修饰的注解会被一同记录到文档中,如果修饰代码元素的注解没有被@Documented注解修饰,那么在生成javadoc文档时,它就不会被记录进去。

    @Inherited
    Inherited有继承的意思,用于表示被@Inherited修饰的注解作用于父类上,子类在继承父类时,可以继承得到被@Inherited修饰的注解。
    这个继承不是表示注解继承注解,关键点是体现在子类继承父类,且通过继承,子类可以获得父类上的注解。
    子类要想不直接使用注解,而是通过继承的方式获得父类上的注解,就需要应用在父类上的注解被@Inherited注解修饰。

    关于@Inherited注解有一些需要注意的地方:
    1)被@Inherited注解修饰的注解作用在类上,子类继承父类时才会继承到父类上被@Inherited注解修饰的注解;
    2)被@Inherited注解修饰的注解作用在方法或者属性上,子类继承父类时,子类的方法或者属性也不会继承得到父类方法或属性上被@Inherited注解修饰的注解;
    3)被@Inherited注解修饰的注解作用在接口上,子接口在继承父接口时,子接口不会继承得到父接口上被@Inherited注解修饰的注解;
    4)被@Inherited注解修饰的注解作用在接口上,子类在实现接口时,子类不会得到接口上被@Inherited注解修饰的注解;

    在子类继承父类或者实现了接口、子接口继承父接口的情况下,通过反射获取到子类或者子接口的代码元素(如Method、Field等),在子类或者子接口没有直接使用注解的情况下,判断这些代码元素上是否存在与父类或者父接口上一样的注解,就可以验证得到上述结论。

    @Repeatable
    先来看一张图,这是@Repeatable注解的定义。
    从源码中可以知道这个元注解是jdk1.8才引入的,value()方法的返回值类型是Class类型。

    Repeatable的意思是可重复的,它作为一个元注解,表示被它修饰的注解可以在某个代码元素上重复使用多次。这个应该怎么理解呢?

    比如我现在定义一个注解,但是这个注解我不使用@Repeatable元注解修饰:

    @Target(ElementType.TYPE_USE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface IsRatable {
    
        String name() default "";
    
    }
    

    我如果想在同一个代码元素上重复多次使用这个注解的话会怎么样?会不会有问题呢?比如我现在要在类上重复使用这个注解,请看下图:

    可以看到注解报错了,同一个注解在同一个代码元素上重复使用确实有问题。

    那如果我确实希望能够实现同一个注解可以在同一个代码元素上重复使用,这个应该怎么解决呢?
    这时候就需要用上jdk1.8新引入的@Repeatable注解了。

    在开始写案例之前我们先考虑一个问题:我们为什么需要一个注解可以被重复使用?

    同一个注解在同一个代码元素上被重复使用,是希望后一次的会覆盖前一次的吗?如果是希望覆盖的话,那我为什么要重复使用呢?我单独使用最新的那个不就行了吗。根据这个思维来考虑,显然这不是重复使用的目的。

    重复使用的目的不是覆盖,那会不会是每次使用注解所存储的数据都会被记录下来放到某个容器里面存储起来呢?也就是说重复使用注解的目的是不是为了通过注解存储多个不同的值呢?嗯,这么一想还真有可能

    例如,一个人可能就会有多重身份,他可能是某人的爸爸,可能是某人的丈夫,还可能是某人的兄弟...
    那如果通过注解来存储一个人的身份的话,这个人只有一个身份,那么注解就只使用一次,只存储一个值,如果这个人有多个身份的话,那么就需要重复使用多次注解,通过多次使用,传入多个不同的身份信息并存储起来。

    我们通过一个例子具体看看,具体理解下:
    首先定义一个可存储角色信息的注解:

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE_USE)
    //使用了@Repeatable元注解修饰@Role注解,并且@Repeatable注解传入的值是Roles.class,
    //因为我们上面说过了@Repeatable注解的返回值类型是Class类型,表示它存储的值是Class类型的,所以这里传入的是字节码对象Roles.class。
    //那为什么传入的是Roles.class而不是Role.class呢?关于这个我们在下面进行解析。
    @Repeatable(Roles.class)
    public @interface Role{
    
        String value() default "";
    
    }
    

    这样我们就定义了一个普通注解@Role,并且使用了@Repeatable元注解进行修饰。
    且元注解只有一个value()方法。

    根据我们上面的分析过程,某个注解要在同一个代码元素上重复多次使用,其目的是为了通过这个注解传入多个值并存储起来。对于多个元素的存储,我们是不是需要依赖容器,比如数组或者集合等。

    注解也是基于这种思想来设计多个元素的存储的:我们要存储多个元素,我们就需要一个创建一个容器来存储它们。
    所以就出现了container annotation,即容器注解这个概念。
    顾名思义,容器注解是指把注解当作一个容器来看代,它可以存储多个元素,但是它存储的元素比较特殊--它存储的是注解。
    所以,理解容器注解需要理解两点:
    1)首先它是一个注解
    2)它存储的元素也是注解

    所以,总结一下容器注解就是用来存储注解的注解。

    然后我们来看看一个容器注解该怎么定义,还是接着上面人有多个身份的案例来设计,定义一个容器注解@roles:

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE_USE)
    public @interface roles{
        
        //定义了一个value()方法,其返回值类型为一个Role[]数组类型,
       //根据前面的定义我们知道,Role也是一个注解,所以这里在一个注解里面通过一个数组容器来
       //存储多个注解信息,所以这就是注解容器。
        Role[] value() default {};
    
    }
    

    有些人看到这里可能会有疑问了,嗯?返回值类型是数组的就是容器注解?那下面定义的这个也是容器注解了???

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Types {
        
        String[] value() default {};
        
    }
    

    这还真不是,如果不信,咱们可以做个实验。
    假设这是容器注解,那么肯定可以通过Repeatable注解应用在其他注解上,我们现在就来试一下能不能成功应用在其他注解上,我们定义一个@Type注解,看下图:

    所以,容器注解并不是方法返回值是一个数组类型就行了,得满足前面说过的两个特点:
    1)首先它本身是一个注解
    2)方法的返回值是注解数组类型

    好了,至此我们可以测试一下我们前面提到的人的身份的案例了,测试例子如下 :
    定义一个Person类,然后在类上重复多次使用被@Repeatable注解修饰的注解@Role,为Person指定多重身份。

    @Role("father")
    @Role("son")
    @Role("brother")
    public class Person {
    
        public static void main(String[] args) {
    
            Roles annotation = Person.class.getAnnotation(Roles.class);
            Role[] roles = annotation.value();
    
            for (Role role : roles){
                String roleName = role.value();
                System.out.println(roleName);
            }
    
        }
    
    }
    

    这回是没有报错的了,可以使用成功,看运行结果,把传入的多个身份都获取到了:

    **容器注解定义的注意事项:
    1)容器注解的定义里必须含有value()方法

    2)容器注解里value()方法的返回值类型必须为注解数组类型

    @Retention
    Retention有保留之意,表示被@Retention修饰的注解会被保留到哪个阶段,这里的阶段主要有3大阶段:源码阶段、字节码阶段、运行时阶段。
    Retention的取值有3种,对应上述的3个阶段,它们在一个枚举声明RetentionPolicy中被定义好了,分别是SOURCE、CLASS、RUNTIME。

    public enum RetentionPolicy {
        
        /**
         * 表示注解保留阶段是源码阶段,即只会在.java代码中存在
         */
        SOURCE,
    
        /**
         * 表示注解保留阶段延续到字节码阶段,即会存在于.java代码以及.class文件中
         */
        CLASS,
    
        /**
         * 表示注解保留阶段延续到运行期间,即注解不仅会在.java代码以及.class文件中保留,而且在程序运行期间还会被加载到jvm内存中,
         * 因此可以做到在程序运行期间获取注解的信息
         */
        RUNTIME
    }
    

    注意:如果开发者在定义注解时,如果不通过@Retention元注解来指定注解的保留阶段,那么默认情况下(也就是不使用@Retention元注解的情况),注解的保留阶段是到字节码阶段的。

    也就是默认情况下,.java代码和.class文件中都会保留注解信息,但是运行时不会加到到内存了。

    你可以自定义一个注解,不使用元注解@Retention修饰,然后运行程序,你看看能不能获取到代码元素上的注解信息,答案是肯定获取不到的。

    @Target
    Target有目标之意,表示被@Target注解修饰的注解到时候可以作用于哪些目标代码元素的。
    例如现在定义一个注解@First,通过@Target来指定这个注解@First可以作用于类、接口、方法和属性:

    @Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
    public @interface First {
        String value() default "";
    }
    

    可以看到@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})的传的参数是一个数组,数组中指定了3个元素,分别是:ElementType.TYPE、ElementType.METHOD、ElementType.FIELD。

    表示我现在定义的这个@First注解最终作用的代码元素只能是类、接口、枚举,或者方法,或者字段这几种代码元素,想要作用于其他的代码元素如方法参数PARAMETER上则会报错,因为@Target没有指定@First注解可以用于方法参数上。

    通过一个例子阐明@Target这个元注解的作用是约束普通注解的作用范围的,一个普通注解可以作用于哪些代码元素,取决于@Target元注解中指定的类型。

    那我们的代码元素到底有哪些呢,或者说@Target的取值到底有哪些呢?它也在枚举声明ElementType中被明确定义好了,如下:

    public enum ElementType {
    
        /**
         * 这个类型表示的是类、接口或者枚举,
         * 意思是,如果@Target注解指定的值是TYPE,那么被@Target注解修饰的注解就可以作用于类、接口或者枚举声明上
         */
        TYPE,
    
       /** 这个类型表示的是属性,
         * 意思是,如果@Target注解指定的值是FIELD,那么被@Target注解修饰的注解就可以作用于属性上
         */
        FIELD,
    
        /** 这个类型表示的是方法,
         * 意思是,如果@Target注解指定的值是METHOD,那么被@Target注解修饰的注解就可以作用于方法上
         */
        METHOD,
    
        /** 这个类型表示的是方法参数,
         * 意思是,如果@Target注解指定的值是PARAMETER,那么被@Target注解修饰的注解就可以作用于方法参数上
         */
        PARAMETER,
    
        /** 这个类型表示的是构造方法,
         * 意思是,如果@Target注解指定的值是CONSTRUCTOR,那么被@Target注解修饰的注解就可以作用于构造方法上
         */
        CONSTRUCTOR,
    
        /** 这个类型表示的是局部变量,
         * 意思是,如果@Target注解指定的值是LOCAL_VARIABLE,那么被@Target注解修饰的注解就可以作用于LOCAL_VARIABLE上,
         * 哪些局部变量呢?比如在方法体内部定义的变量
         */
        LOCAL_VARIABLE,
    
        /** 这个类型表示的是注解,
         * 意思是,如果@Target注解指定的值是ANNOTATION_TYPE,那么被@Target注解修饰的注解就可以作用于注解上,
         * 注意请仔细看这里,这个说明如果通过@Target指定的类型是ANNOTATION_TYPE的话,那么当前定义的这个注解就是个元注解,
         * 它到时候是要作用于普通注解上的
         */
        ANNOTATION_TYPE,
    
        /** 这个类型表示的是java包,
         * 意思是,如果@Target注解指定的值是PACKAGE,那么被@Target注解修饰的注解就可以作用于java包上
         */
        PACKAGE,
    
        /**
         * TYPE_PARAMETER的意思是类型参数,类型参数是啥呢?它就是泛型。
         * 泛型把类型当作一种参数来看代。
         * 如果@Target注解指定的值是TYPE_PARAMETER,那么被@Target注解修饰的注解就可以作用于泛型上,
         *
         * @since 1.8 (通过这个注释可以知道,这个类型是在jdk1.8才引入的)
         */
        TYPE_PARAMETER,
    
        /**
         * TYPE_USE类型是什么呢?
         * 它是用于声明注解可以作用于任何类型的代码元素的。
         * 这怎么理解呢?
         * 我们在上面说过同一个注解是可以作用于多种类型的代码元素上的,如果我们要作用于多种代 
         * 码元素,那么我们可能需要通过@Target指定多种元素类型,现在在jdk1.8引入了TYPE_USE
         * 这个类型之后,我们的写法可以简单一些了:
         * 如果想指定注解可作用于所有类型的代码元素,不用像之前那样把每一种代码元素对应的类型 
         * 都传给@Target注解了,只需要对@Target指定TYPE_USE类型即可使注解对所有类型的代
         * 码元素生效。
         *
         * @since 1.8(通过这个注释可以知道,这个类型是在jdk1.8才引入的)
         */
        TYPE_USE
    }
    

    我们查看一下@Target元注解的定义:

    @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[],这说明@Target元注解可以传入多种代码元素类型,意味着它修饰的注解将可以作用于多种代码元素。

    注意:
    1)如果开发者在定义一个注解时,没有使用@Target元注解进行修饰,那么默认这个注解可作用于所有类型的代码元素(不包括泛型)
    2)不使用@Target元注解修饰与使用@Target元注解修饰并指定传入类型为TYPE_USE这两种情况的作用差不多,区别在于:
    不使用@Target元注解修饰的话,可作用于除了泛型之外的所有类型的代码元素;
    而使用@Target元注解修饰并指定传入类型为TYPE_USE的话,对所有类型的代码元素都生效,包括泛型。

    有的小伙伴可能会有疑问,我看图片的列表里还有@Native这个注解啊,它不是元注解吗?为什么不介绍它呢?
    其实啊,@Native这个注解还真不是元注解,我们可以点击进入@Native注解的源码看一下:

    @Documented
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Native {
    }
    

    可以看到修饰@Native注解使用的元注解@Target(ElementType.FIELD)指示出@Native注解的作用对象类型是FIELD,也就是属性而不是注解ANNOTATION_TYPE,那么它就只能用来修饰属性而不能用来修饰注解;
    用来修饰注解的注解才叫元注解,所以@Native注解不是元注解。

    哪些又是普通注解?
    java中常见的普通注解有:
    @Override
    @Deprecated
    @SuppressWarnings

    @Override
    首先我们看下@Override注解在jdk源码中的定义:

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Override {
    }
    

    通过@Target元注解指定@Override注解的目标代码元素是METHOD,通过@Retention元注解指定@Override注解的保留阶段是源码阶段,即只会保留在.java代码中。

    通过这个定义我们知道@Override注解是只能作用于方法元素的,它的作用是用于标识某个方法是重写的父类方法。
    那么@Override注解其实就相当于一个标识,编译器在编译阶段通过这个标识知道这个方法是重写自父类的,那么就需要检查其父类中是否定义了这个方法,因为只有父类中定义了,子类才有重写这一说法,否则在编译阶段要抛出异常,让开发者尽早知道问题并解决问题。

    @Deprecated
    看下@Deprecated注解在jdk源码中的定义:

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
    public @interface Deprecated {
    }
    

    它的保留阶段持续到运行时,可作用代码元素包括:CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE。
    它的作用是用于标识某个代码元素在当前的版本中已经过时了,不建议再使用。

    如图,过时的元素会在中部加一条线,提示这个元素过时了。

    @SuppressWarnings
    看下@SuppressWarnings注解在jdk源码中的定义:

    @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
    @Retention(RetentionPolicy.SOURCE)
    public @interface SuppressWarnings {
       
        String[] value();
    }
    

    @SuppressWarnings注解保留阶段为源码阶段,@SuppressWarnings注解的可作用代码元素类型为:TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE。

    @SuppressWarnings注解value()方法的返回值是String[]数组类型,所以@SuppressWarnings注解可以传入单个String类型值也可以传入一个String数组,如:

    还可以这样传值:

    @SuppressWarnings注解的作用是:告诉编译器,当代码元素上使用了@SuppressWarnings注解时,如果该代码元素在编译过程中发生了@SuppressWarnings注解指定的告警类型的话,那么编译器需要忽略该类型的告警信息。
    具体忽略哪种类型的告警信息,通过@SuppressWarnings传入的告警类型决定。

    @SuppressWarnings注解可传入的常见告警类型整理如下:

    unchecked:抑制未检查的转化,例如集合没有指定类型的警告
    unused:抑制未使用的变量的警告
    resource:抑制与使用Closeable类型资源相关的警告
    path:抑制在类路径,原文件路径中有不存在的路径的警告
    deprecation:抑制使用了某些不赞成使用的类和方法的警告
    fallthrough:抑制switch语句执行到底没有break关键字的警告
    serial:抑制某类实现Serializable,但是没有定义serialVersionUID,这个需要但是不必须的字段的警告
    rawtypes:抑制没有传递带有泛型的参数的警告
    all:抑制全部类型的警告
    

    ————————————————————————————————————————————————

    这里讲解的这些注解都是作用于非注解的代码元素的,所以它们都是普通注解,并且由于它们是java软件包中已经事先定义好的了,所以它们也是java的内置注解。

    2、java如何自定义注解?

    在前面的那一小节中,我其实已经多次使用了自定义注解了,但是有些规范以及注意事项我还想单独通过一个小节进行讲解。

    首先我们应该认识到的一点是:java内置注解与自定义注解本质上没有区别,如果硬要找一个不同点的话,那么就是内置注解是jdk发包时就已经定义好的了,已经存在于java的软件包当中,而自定义注解则是由于开发人员根据不同的需求自己定义的。

    如何自定义一个注解:
    1)创建一个新的.java文件,并指定类型为Annotation类型。

    2)定义得到的注解如下:
    使用元注解@Retention指定注解的保留阶段,或者说生命周期,如这里指定的保留阶段是运行时阶段。
    使用元注解@Target指定注解可作用的代码元素类型,如这里指定的代码元素类型是ElementType.TYPE,表示可作用于类、接口或者枚举声明。

    因为一般情况下,我们自定义注解都是针对特定的需求来设计的,所以一般都会通过相关的元注解修饰进而限定自定义注解的使用。

    如果不使用@Retention和@Target元注解的话,那么自定义注解的生命周期默认是字节码阶段,而作用范围默认是所有类型的代码元素(不包括泛型)。

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Person {
        
        String value() default "";
    }
    

    根据上面注解的定义,我们可以看到:
    1)注解的定义与接口的定义很相近,但是注解需要在interface前面添加@符号,也就是注解定义的关键字是@interface
    2)注解中声明的方法可以通过default关键字指定默认的返回值,但是指定默认返回值并不是必要的

    如果定义注解时使用default关键字指定了默认值:
    如果在使用注解时不传入新值,则使用默认值,如果传入了新值,则使用传入的值覆盖默认值;
    如果定义注解时没有使用default关键字指定默认值:
    那么在使用注解时必须要传入数值,否则报错。

    注解中方法的返回值类型有哪些?
    注解中方法支持的返回值类型如下:
    1)java的所有基本数据类型(int、float、boolean、byte、double、char、long、short)

    2)字符串类型 String

    3)字节码类型 Class

    4)枚举类型 enum

    5)注解类型 Annotation

    6)数组类型,其中数组元素的类型只能是上述类型之一

    也就是说,注解方法的返回值类型不能是对象引用类型,例如:
    我定义一个类Person

    public class Person {
    
    }
    

    再定义一个注解@Role

    在Role注解中使用Person类型,结果报错了,注解的返回值类型中没有Person这种类型

    自定义注解本身能被继承?
    自定义注解本身是不能被继承的,例如,我定义一个注解@A,再定义一个注解@B,并使注解A继承注解B,如下图:

    报错了,自定义注解无法被继承。

    但是所有的注解最终都需要继承java.lang.annotation.Annotation接口

    对注解@Role的字节码文件反编译,可以得到上图的信息,从图中可以看到,注解Role继承了接口java.lang.annotation.Annotation。

    注解中方法的声明需要注意什么吗?
    我们在使用注解并传入数值时,一般是通过key=value的格式进行传值的,其中key对应的是方法名,value对应的是传给该方法的值,如@Role注解:

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Role {
    
        String role() default "";
    
    }
    

    在类Person上使用@Role注解:

    //Role注解的方法名为role,所以传数值时key为注解的目标方法名role,value为需要传给注解存储的值,即role="father"
    @Role(role = "father")
    public class Person {
    
    }
    

    如果注解中声明了一个或多个方法,并且至少有一个方法名为value,那么在使用注解传值时可以省略key,直接传入数值;
    即不管一个注解中声明了多少个方法,只要其中含有名为value的方法,那么传数值时都可以省略key,自然地,省略key情况下,传入的数值都是传给value方法的,想要给其他方法传值,那么就需要显式地通过key=value方式指定了;
    当然,对value方法传值可以省略key,自然也可以指定key了,如果想要指定key的话,根据key为方法名的规则,指定的key为value:

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Role {
    
        //声明了value()方法
        String value() default "";
       
        //声明了test()方法
        String test() default "";
    
    }
    

    在类Person上使用Role注解:
    省略key的情况,直接传入数值

    @Role("father")
    public class Person {
        
        public static void main(String[] args) {
            //调用Role注解的value()方法获取值,预期是可以获取到father的
            System.out.println(Person.class.getAnnotation(Role.class).value());
        }
    }
    

    运行结果与预期一致,获取到了father,证明省略key的情况下,数值是传给value()方法的;
    调用Role注解的test()方法的话,无法获取到father:

    对value方法显式指定key为value

    @Role(value="father")
    public class Person {
        
        public static void main(String[] args) {
            System.out.println(Person.class.getAnnotation(Role.class).test());
        }
    }
    

    调用Role注解的value方法也可以获取到father:

    3、注解都可以用在哪里?

    第一小节中讲解@Target元注解的时候已经讲到了注解可以作用的代码元素有哪些,这里再贴一边代码:

    public enum ElementType {
    
        /**
         * 这个类型表示的是类、接口或者枚举,
         * 意思是,如果@Target注解指定的值是TYPE,那么被@Target注解修饰的注解就可以作用于类、接口或者枚举声明上
         */
        TYPE,
    
       /** 这个类型表示的是属性,
         * 意思是,如果@Target注解指定的值是FIELD,那么被@Target注解修饰的注解就可以作用于属性上
         */
        FIELD,
    
        /** 这个类型表示的是方法,
         * 意思是,如果@Target注解指定的值是METHOD,那么被@Target注解修饰的注解就可以作用于方法上
         */
        METHOD,
    
        /** 这个类型表示的是方法参数,
         * 意思是,如果@Target注解指定的值是PARAMETER,那么被@Target注解修饰的注解就可以作用于方法参数上
         */
        PARAMETER,
    
        /** 这个类型表示的是构造方法,
         * 意思是,如果@Target注解指定的值是CONSTRUCTOR,那么被@Target注解修饰的注解就可以作用于构造方法上
         */
        CONSTRUCTOR,
    
        /** 这个类型表示的是局部变量,
         * 意思是,如果@Target注解指定的值是LOCAL_VARIABLE,那么被@Target注解修饰的注解就可以作用于LOCAL_VARIABLE上,
         * 哪些局部变量呢?比如在方法体内部定义的变量
         */
        LOCAL_VARIABLE,
    
        /** 这个类型表示的是注解,
         * 意思是,如果@Target注解指定的值是ANNOTATION_TYPE,那么被@Target注解修饰的注解就可以作用于注解上,
         * 注意请仔细看这里,这个说明如果通过@Target指定的类型是ANNOTATION_TYPE的话,那么当前定义的这个注解就是个元注解,
         * 它到时候是要作用于普通注解上的
         */
        ANNOTATION_TYPE,
    
        /** 这个类型表示的是java包,
         * 意思是,如果@Target注解指定的值是PACKAGE,那么被@Target注解修饰的注解就可以作用于java包上
         */
        PACKAGE,
    
        /**
         * TYPE_PARAMETER的意思是类型参数,类型参数是啥呢?它就是泛型。
         * 泛型把类型当作一种参数来看代。
         * 如果@Target注解指定的值是TYPE_PARAMETER,那么被@Target注解修饰的注解就可以作用于泛型上,
         *
         * @since 1.8 (通过这个注释可以知道,这个类型是在jdk1.8才引入的)
         */
        TYPE_PARAMETER,
    
        /**
         * TYPE_USE类型是什么呢?
         * 它是用于声明注解可以作用于任何类型的代码元素的。
         * 这怎么理解呢?
         * 我们在上面说过同一个注解是可以作用于多种类型的代码元素上的,如果我们要作用于多种代 
         * 码元素,那么我们可能需要通过@Target指定多种元素类型,现在在jdk1.8引入了TYPE_USE
         * 这个类型之后,我们的写法可以简单一些了:
         * 如果想指定注解可作用于所有类型的代码元素,不用像之前那样把每一种代码元素对应的类型 
         * 都传给@Target注解了,只需要对@Target指定TYPE_USE类型即可使注解对所有类型的代
         * 码元素生效。
         *
         * @since 1.8(通过这个注释可以知道,这个类型是在jdk1.8才引入的)
         */
        TYPE_USE
    }
    

    需要注意的是,TYPE_USE并不是代码元素类别,而是所有类型的代码元素的一个统称。
    即要指代对所有类型的代码元素有效时,可以直接使用TYPE_USE。
    除了TYPE_USE,其余的枚举值都表示代码元素。

    参考文章 :
    https://blog.csdn.net/javazejian/article/details/71860633?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.not_use_machine_learn_pai&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.not_use_machine_learn_pai
    https://cloud.tencent.com/developer/article/1353329

    相关文章

      网友评论

          本文标题:帮你搞懂java注解(二)

          本文链接:https://www.haomeiwen.com/subject/hrzuoktx.html