精华内容
下载资源
问答
  • 项目简介 word-checker 用于英文单词拼写检查 创作目的 平时工作学习中对于单词拼写检查也很常见的需求 一直没找到特别好用的版本就自己写一个方便以后拓展和他人使用 快速开始 JDK 版本 JDK1.7 及其以后 入门例子...
  • 问题引入 平时工作学习中对于单词拼写检查也很常见的需求 一直没找到特别好用的版本就自己写一个方便以后拓展和他人使用 项目简介 本项目用于英文单词拼写检查 word-checker 快速开始 JDK 版本 JDK1.7 及其以后 ...
  • 注解是什么 注解也叫元数据,例如我们常见的@Override和@Deprecated,注解是JDK1.5版本开始引入的一个特性,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解。 注解可分为三类: 一类...

    注解是什么

    注解也叫元数据,例如我们常见的@Override和@Deprecated,注解是JDK1.5版本开始引入的一个特性,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解。
    注解可分为三类:

    • 一类是Java自带的标准注解,包括@Override(标明重写某个方法)、@Deprecated(标明某个类或方法过时)和@SuppressWarnings(标明要忽略的警告),使用这些注解后编译器就会进行检查。
    • 一类为元注解,元注解是用于定义注解的注解,包括@Retention(标明注解被保留的阶段)、@Target(标明注解使用的范围)、@Inherited(标明注解可继承)、@Documented(标明是否生成javadoc文档)
    • 一类为自定义注解,可以根据自己的需求定义注解

    注解有什么用

    比如说hibernate的@Entity.框架会把这个类判定为一个entity
    你不用再去写.xml配置文件了呀
    或者说springMVC的@Controller,他会自动把这个类识别为一个控制器…总之就是省事儿.

    展开全文
  • 所有知识体系文章,GitHub已收录,欢迎Star!...它JDK1.5及以后版本引入的一个特性,与类、接口、枚举在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,...

    所有知识体系文章,GitHub已收录,欢迎Star!再次感谢,愿你早日进入大厂!

    Ziphtracks/JavaLearningmanualgithub.com
    ea57575e96c4d88c02db5341237779af.png

    一、Java注解概述

    注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。

    二、注解的作用分类

    • 编写文档: 通过代码里标识的元数据生成文档【生成文档doc文档】
    • 代码分析: 通过代码里标识的元数据对代码进行分析【使用反射】
    • 编译检查: 通过代码里标识的元数据让编译器能够实现基本的编译检查【Override等】

    编写文档

    首先,我们要知道Java中是有三种注释的,分别为单行注释、多行注释和文档注释。而文档注释中,也有@开头的元注解,这就是基于文档注释的注解。我们可以使用javadoc命令来生成doc文档,此时我们文档的内元注解也会生成对应的文档内容。这就是编写文档的作用。

    代码分析

    我们频繁使用之一,也是包括使用反射来通过代码里标识的元数据对代码进行分析的,此内容我们在后续展开讲解。

    编译检查

    至于在编译期间在代码中标识的注解,可以用来做特定的编译检查,它可以在编译期间就检查出“你是否按规定办事”,如果不按照注解规定办事的话,就会在编译期间飘红报错,并予以提示信息。可以就可以为我们代码提供了一种规范制约,避免我们后续在代码中处理太多的代码以及功能的规范。比如,@Override注解是在我们覆盖父类(父接口)方法时出现的,这证明我们覆盖方法是继承于父类(父接口)的方法,如果该方法稍加改变就会报错;@FunctionInterface注解是在编译期检查是否是函数式接口的,如果不遵循它的规范,同样也会报错。

    三、jdk的内置注解

    3.1 内置注解分类

    • @Override: 标记在成员方法上,用于标识当前方法是重写父类(父接口)方法,编译器在对该方法进行编译时会检查是否符合重写规则,如果不符合,编译报错。
    • @Deprecated: 用于标记当前类、成员变量、成员方法或者构造方法过时如果开发者调用了被标记为过时的方法,编译器在编译期进行警告。
    • @SuppressWarnings: 压制警告注解,可放置在类和方法上,该注解的作用是阻止编译器发出某些警告信息。
    • @Repeatable: 表明标记的注解可以多次应用于相同的声明或类型,此注解由Java8版本引入。

    3.2 @Override注解

    标记在成员方法上,用于标识当前方法是重写父类(父接口)方法,编译器在对该方法进行编译时会检查是否符合重写规则,如果不符合,编译报错。

    这里解释一下@Override注解,在我们的Object基类中有一个方法是toString方法,我们通常在实体类中去重写此方法来达到打印对象信息的效果,这时候也会发现重写的toString方法上方就有一个@Override注解。如下所示:

    7b444383311cddeff2e0d208243c0d9b.png

    于是,我们试图去改变重写后的toString方法名称,将方法名改为toStrings。你会发现在编译期就报错了!如下所示:

    240d5e25787395af3649ac23c3a7e168.png

    那么这说明什么呢?这就说明该方法不是我们重写其父类(Object)的方法。这就是@Override注解的作用。

    3.3 @Deprecated注解

    用于标记当前类、成员变量、成员方法或者构造方法过时如果开发者调用了被标记为过时的方法,编译器在编译期进行警告。

    我们解释@Deprecated注解就需要模拟一种场景了。假设我们公司的产品,目前是V1.0版本,它为用户提供了show1方法的功能。这时候我们为产品的show1方法的功能又进行了扩展,打算发布V2.0版本。但是,我们V1.0版本的产品需要抛弃吗?也就是说我们V1.0的产品功能还继续让用户使用吗?答案肯定是不能抛弃的,因为有一部分用户是一直用V1.0版本的。如果抛弃了该版本会损失很多的用户量,所以我们不能抛弃该版本。这时候,我们对功能进行了扩展后,发布了V2.0版本,我们给予用户的通知就可以了,也就是告知用户我们在V2.0版本中为功能进行了扩展。可以让用户自行选择版本。

    但是,除了发布告知用户版本情况之外,我们还需要在原来版本的功能上给予提示,在上面的模拟场景中我们需要在show1方法上方加@Deprecated注解给予提示。通过这种方式也告知用户“这是旧版本时候的功能了,我们不建议再继续使用旧版本的功能”,这句话的意思也就正是给用户做了提示。用户也会这么想“奥,这版本的这个功能不好用了,肯定有新版本,又更好用的功能。我要去官网查一下下载新版本”,还会有用户这么想“我明白了,又更新出更好的功能了,但是这个版本的功能我已经够用了,不需要重新下载新版本了”。

    e5f7708092eb00e390b49758afab105c.png

    那么我们怎么查看我上述所说的在功能上给予的提示呢?这时候我需要去创建一个方法,然后去调用show1方法,并查看调用时它是如何提示的。

    09ecb6422123b4875dc62c532232e3dc.png

    图已经贴出来了,你是否发现的新旧版本功能的异同点呢?很明显,在方法中的提示是在调用的方法名上加了一道横线把该方法划掉了。这就体现了show1方法过时了,已经不建议使用了,我们为你提供了更好的。

    回想起来,在我们的api中也会有方法是过时的,比如我们的Date日期类中的方法有很多都已经过时了。如下图:

    ffbcefc828f76611514b69c11bb3b3d2.png

    0f1d250eb38234be4c088ed63e0336f7.png

    如你所见,是不是有很多方法都过时了呢?那它的方法上是加了@Deprecated注解吗?来跟着我的脚步,我带你们看一下。

    39040c8af0f6e31a470aff55f21eb36d.png

    我们已经知道的Date类中的这些方法已经是过时的了,如果我们使用该方法并执行该程序的话。执行的过程中就会提示该方法已过时的内容,但是只是提示,并不影响你使用该方法。如下:

    a30487869996744aa32d0aae8eb574b4.png

    OK!这也就是@Deprecated注解的作用了。

    3.4 @SuppressWarnings注解

    压制警告注解,可放置在类和方法上,该注解的作用是阻止编译器发出某些警告信息,该注解为单值注解,只有 一个value参数,该参数为字符串数组类型,参数值常用的有如下几个。
    • unchecked:未检查的转化,如集合没有指定类型还添加元素
    • unused:未使用的变量
    • resource:有泛型未指定类型
    • path:在类路径,原文件路径中有不存在的路径
    • deprecation:使用了某些不赞成使用的类和方法
    • fallthrough:switch语句执行到底没有break关键字
    • rawtypes:没有写泛型,比如: List list = new ArrayList();
    • all:全部类型的警告

    压制警告注解,顾名思义就是压制警告的出现。我们都知道,在Java代码的编写过程中,是有很多黄色警告出现的。但是我不知道你的导师是否教过你,程序员只需要处理红色的error,不需要理会黄色的warning。如果你的导师说过此问题,那是有原因的。因为在你学习阶段,我们认清处理红色的error即可,这样可以减轻你学习阶段在脑部的记忆内容。如果你刚刚加入学习Java的队列中,需要大脑记忆的东西就有太多了,也就是我们目前不需要额外记忆其他的东西,只记忆重点即可。至于黄色warning嘛,在你的学习过程中慢慢就会有所了解的,而不是死记硬背的。

    那为了解释@SuppressWarnings注解,我们还使用上一个例子,因为在那个例子中就有黄色的warning出现。

    02b0d7b2d8938760878beeeac99f54ab.png

    而每一个黄色的warning都会有警告信息的。比如,这一个图中的警告信息,就告知你show2()方法没有被使用,简单来说,你创建的show2方法,但是你在代码中并没有调用过此方法。以后你便会遇到各种各样黄色的warning。然后, 我们就可以使用不同的注解参数来压制不同的注解。但是在该注解的参数中,提供了一个all参数可以压制全部类型的警告。而这个注解是需要加到类的上方,并赋予all参数,即可压制所有警告。如下:

    91e2b4441f4f24b9262b27eba2331ba4.png

    我们加入注解并赋予all参数后,你会发现use方法和show2方法的警告没有了,实际上导Date包的警告还在,因为我们Date包导入到了该类中,但是我们并没有创建Date对象,也就是并没有写入Date在代码中,你也会发现那一行是灰色的,也就证明了我们没有去使用导入这个包的任何信息的说法,出现这种情况我们就需要把这个没有用的导包内容删除掉,使用Ctrl + X删除导入没有用到的包即可。还有一种办法就是在包的上方修饰压制警告注解,但是我认为在一个没有用的包上加压制注解是毫无意义的,所以,我们直接删除就好。

    然后,我们还见到上图,注解那一行出现了警告信息提示。这一行的意思是冗余的警告压制。这就是说我们压制以下的警告并没有什么意义而造成的冗余,但是如果我们使用了该类并做了点什么的话,压制注解的冗余警告就会消失,毕竟我们使用了该类,此时就不会早场冗余了。

    上述解释@SuppressWarnings注解也差不多就这些了。OK,继续向下看吧。持续为大家讲解。

    3.5 @Repeatable注解

    @Repeatable 表明标记的注解可以多次应用于相同的声明或类型,此注解由Java8版本引入。我们知道注解是不能重复定义的,其实该注解就是一个语法糖,它可以重复多此使用,更适用于我们的特殊场景。

    首先,我们先创建一个可以重复使用的注解。

    package com.mylifes1110.anno;
    
    import java.lang.annotation.Repeatable;
    
    @Repeatable(Hour.class)
    public @interface Hours {
        double[] hours() default 0;
    }

    你会发现注解要求传入的值是一个类对象,此类对象就需要传入另外一个注解,这里也就是另外一个注解容器的类对象。我们去创建一下。

    package com.mylifes1110.anno;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    //容器
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Hour {
        Hours[] value();
    }

    其实,这两个注解的套用,就是将一个普通的注解封装了一个可重复使用的注解,来达到注解的复用性。最后,我们创建一下测试类,随后带你去看一下源码。

    package com.mylifes1110.java;
    
    import com.mylifes1110.anno.Hours;
    
    @Hours(hours = 4)
    @Hours(hours = 4.5)
    @Hours(hours = 2)
    public class Worker {
        public static void main(String[] args) {
            //通过Hours注解类型来获取Worker中的值数组对象
            Hours[] hours = Worker.class.getAnnotationsByType(Hours.class);
            //遍历数组
            for (Hours h : hours) {
                System.out.println(h);
            }
        }
    }

    测试类,是一个工人测试类,该工人使用注解记录早中晚的工作时间。测试结果如下:

    0f911b077dae944cdeb81f487dba0668.png

    然后我们进入到源码一探究竟。

    ff93a2338644615a36f6a53a9e5aa922.png

    我们发现进入到源码后,就只看见一个返回值为类对象的抽象方法。这也就验证了该注解只是一个可实现重复性注解的语法糖而已。

    四、注解分类

    4.1 注解分类

    注解可以根据注解参数分为三大类:
    • 标记注解: 没有参数的注解,仅用自身的存在与否为程序提供信息,如@Override注解,该注解没有参数,用于表示当前方法为重写方法。
    • 单值注解: 只有一个参数的注解,如果该参数的名字为value,那么可以省略参数名,如 @SuppressWarnings(value = "all"),可以简写为@SuppressWarnings("all")。
    • 完整注解: 有多个参数的注解。

    4.2 标记注解

    说到@Override注解是一个标记注解,那我们进入到该注解的源码查看一下。从上往下看该注解源码,发现它继承了导入了java.lang.annotation.*,也就是有使用到该包的内容。然后下面就又是两个看不懂的注解,其实发现注解的定义格式是public修饰的@Interface,最终看到该注解中方法体并没有任何参数,也就是只起到标记作用。

    5a43b2f1287cc47a757ceee8155e6d99.png

    4.3 单值注解

    在上面我们用到的@SuppressWarnings注解就是一个单值注解。那我们进入到它的源码看一下是怎么个情况。其实,和标记注解比较,它就多一个value参数而已,而这就是单值注解的必要条件,即只有一个参数。并且这一个参数为value时,我们可以省略value。

    2efd8f952421fd5588dcc6fd5fccf3de.png

    4.4 完整注解

    上述两个类型注解讲解完,至于完整注解嘛,这下就能更明白了。其中的方法体就是有多个参数而已。

    五、自定义注解

    5.1 自定义注解格式

    格式: public @Interface 注解名 {属性列表/无属性}注意: 如果注解体中无任何属性,其本质就是标记注解。但是与其标注注解还少了上边修饰的元注解。

    如下,这就是一个注解。但是它与jdk自定义注解有点区别,jdk自定义注解的上方还有注解来修饰该注解,而那注解就叫做元注解。元注解我会在后面详细的说到。

    606bcaebf57929ada2df66cc2ddff65d.png

    这里我们的确不知道@Interface是什么,那我们就把自定义的这个注解反编译一下,看一下反编译信息。反编译操作如下:

    274c0a08a4f6b22f5fb2a558dcfbb49b.png

    反编译后的反编译内容如下:

    public interface com.mylifes1110.anno.MyAnno extends java.lang.annotation.Annotation {
    }

    首先,看过反编译内容后,我们可以直观的得知他是一个接口,因为它的public修饰符后面的关键字是interface。

    其次,我们发现MyAnno这个接口是继承了java.lang.annotation包下的Annotation接口。

    所以,我们可以得知注解的本质就是一个接口,该接口默认继承了Annotation接口。

    既然,是继承的Annotation接口,那我们就去进入到这个接口中,看它定义了什么。以下是我抽取出来的接口内容。我们发现它看似很常见,其实它们不是很常用,作为了解即可。

    public interface Annotation {
        boolean equals(Object obj);
        int hashCode();
        String toString();
        Class<? extends Annotation> annotationType();
    }

    最后,我们的注解中也是可以写有属性的,它的属性不同于普通的属性,它的属性是抽象方法。既然注解也是一个接口,那么我们可以说接口体中可以定义什么,它同样也可以定义,而它的修饰符与接口一样,也是默认被public abstract修饰。

    而注解体中的属性也是有要求的。其属性要求如下:
    • 属性的返回值类型必须是以下几种:
      • 基本数据类型
      • String类型
      • 枚举类型
      • 注解
      • 以上类型的数组
      • 注意: 在这里不能有void的无返回值类型和以上类型以外的类型
    • 定义的属性,在使用时需要给注解中的属性赋值
      • 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时可以不为属性赋值,它取的是默认值。如果为它再次传入值,那么就发生了对原值的覆盖。
      • 如果只有一个属性需要赋值,并且属性的名称为value,则赋值时value可以省略,可以直接定义值
      • 数组赋值时,值使用{}存储值。如果数组中只有一个值,则可以省略{}

    5.2 自定义注解属性的返回值

    属性返回值既然有以上几种,那么我就在这里写出这几种演示一下是如何写的。

    首先,定义一个枚举类和另外一个注解备用。

    package com.mylifes1110.enums;
    
    public enum Lamp {
        RED, GREEN, YELLOW
    }
    package com.mylifes1110.anno;
    
    public @interface MyAnno2 {
    }

    其次,我们来定义上述几种类型,如下:

    package com.mylifes1110.anno;
    
    import com.mylifes1110.enums.Lamp;
    
    public @interface MyAnno {
        //基本数据类型
        int num();
    
        //String类型
        String value();
    
        //枚举类型
        Lamp lamp();
    
        //注解类型
        MyAnno2 myAnno2();
        
        //以上类型的数组
        String[] values();
        Lamp[] lamps();
        MyAnno2[] myAnno2s();
        int[] nums();
    }

    5.3 自定义注解的属性赋值

    这里我们演示一下,首先,我们使用该注解来进行演示。

    package com.mylifes1110.anno;
    
    public @interface MyAnno {
        //基本数据类型
        int num();
    
        //String类型
        String value();
    }

    随后创建一个测试类,在类的上方写上注解,你会发现,注解的参数中会让你写这两个参数(int、String)。

    1b7fac83ed9f96a32774724a30ecf03e.png

    此时,传参是这样来做的。格式为:名称 = 返回值类型参数。如下:

    65975a5d01011553e7ea49e5e2086910.png

    上述所说,如果使用default关键字给属性默认初始化值,就不需要为其参数赋值,如果赋值的话,就把默认初始化的值覆盖掉了。

    3519674c10d22d6b0153cfb6da23ba73.png

    当然还有一个规则,如果只有一个属性需要赋值,并且属性的名称为value,则赋值时value可以省略,可以直接定义值。那么,我们的num已经有了默认值,就可以不为它传值。我们发现,注解中定义的属性就剩下了一个value属性值,那么我们就可以来演示这个规则了。

    e72b1d6b575900d83c41216f9573cc95.png

    这里,我并没有写属性名称value,而是直接为value赋值。如果我将num的default关键字修饰去掉呢,那意思也就是说在使用该注解时必须为num赋值,这样可以省略value吗?那我们看一下。

    d9913ab9b98f12c0365ff2d596a88732.png

    结果,就是我们所想的,它报错了,必须让我们给num赋值。其实想想这个规则也是很容易懂的,定义一个为value的值,就可以省略其value名称。如果定义多个值,它们可以省略名称就无法区分定义的是那个值了,关键是还有数组,数组内定义的是多个值呢,对吧。

    5.4 自定义注解的多种返回值类型赋值

    这里我们演示一下,上述的多种返回值类型是如何赋值的。这里我们定义这几个参数来看一下,是如何为属性赋值的。

    5d820d69fdf49ba1835034e451aba620.png

    num是一个int基本数据类型,即num = 1

    value是一个String类型,即value = "str"

    lamp是一个枚举类型,即lamp = Lamp.RED

    myAnno2是一个注解类型,即myAnno2 = @MyAnno2

    values是一个String类型数组,即values = {"s1", "s2", "s3"}

    values是一个String类型数组,其数组中只有一个值,即values = "s4"

    注意: 值与值之间是,隔开的;数组是用{}来存储值的,如果数组中只有一个值可以省略{};枚举类型是枚举名.枚举值

    2b4e3a183c74a683dc43b3062247a52c.png

    六、元注解

    6.1 元注解分类

    元注解就是用来描述注解的注解。一般使用元注解来限制自定义注解的使用范围、生命周期等等。

    而在jdk的中java.lang.annotation包中定义了四个元注解,如下:

    元注解描述@Target指定被修饰的注解的作用范围@Retention指定了被修饰的注解的生命周期@Documented指定了被修饰的注解是可以Javadoc等工具文档化@Inherited指定了被修饰的注解修饰程序元素的时候是可以被子类继承的

    6.2 @Target

    @Target 指定被修饰的注解的作用范围。其作用范围可以在源码中找到参数值。

    属性描述CONSTRUCTOR用于描述构造器FIELD(常用)用于描述属性LOCAL_VARIABLE用于描述局部变量METHOD(常用)用于描述方法PACKAGE用于描述包PARAMETER用于描述参数TYPE(常用)用于描述类、接口(包括注解类型) 或enum声明ANNOTATION_TYPE用于描述注解类型TYPE_USE用于描述使用类型

    由此可见,该注解体内只有一个value属性值,但是它的类型是一个ElementType数组。那我们进入到这个数组中继续查看。

    e5e7aac61462473ce3f24d0988e37f08.png

    进入到该数组中,你会发现他是一个枚举类,其中定义了上述表格中的各个属性。

    75541856faceff1346c5c866a6de1500.png

    了解了@Target的作用和属性值后,我们来使用一下该注解。首先,我们要先用该注解来修饰一个自定义注解,定义该注解的指定作用在类上。如下:

    dd4810797ee9c21669764dd385407d52.png

    而你观察如下测试类,我们把注解作用在类上时是没有错误的。而当我们的注解作用在其他地方就会报错。这也就说明了,我们@Target的属性起了作用。

    注意: 如果我们定义多个作用范围时,也是可以省略该参数名称了,因为该类型是一个数组,虽然能省略名称但是,我们还需要用{}来存储。

    2cec40a0b9819ff7fa8f6674840a2e7a.png

    6.3 @Retention

    @Retention 指定了被修饰的注解的生命周期

    属性描述RetentionPolicy.SOURCE注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。RetentionPolicy.CLASS注解只被保留到编译进行时的class文件,但 JVM 加载class文件时候被遗弃,也就是在这个阶段不会读取到该class文件。RetentionPolicy.RUNTIME(常用)注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。

    注意: 我们常用的定义即是RetentionPolicy.RUNTIME,因为我们使用反射来实现的时候是需要从JVM中获取class类对象并操作类对象的。

    首先,我们要了解反射的三个生命周期阶段,这部分内容我在Java反射机制中也是做了非常详细的说明,有兴趣的小伙伴可以去看看我写的Java反射机制,相信你在其中也会有所收获。

    这里我再次强调一下这三个生命周期是源码阶段 - > class类对象阶段 - > Runtime运行时阶段

    那我们进入到源码,看看@Retention注解中是否有这些参数。

    9edfef75381b6b63c8c65bc1cbc6664b.png

    我们看到该注解中的属性只有一个value,而它的类型是一个RetentionPolicy类型,我们进入到该类型中看看有什么参数,是否与表格中的参数相同呢?

    db0dbfae4f0e356d59b88f1380be8bd9.png

    至于该注解怎么使用,其实是相同的,用法如下:

    89a5466c414c2050310741a873e5bc8a.png

    这就证明了我们的注解可以保留到Runtime运行阶段,而我们在反射中大多数是定义到Runtime运行时阶段的,因为我们需要从JVM中获取class类对象并操作类对象。

    6.4 @Documented

    @Documented 指定了被修饰的注解是可以Javadoc等工具文档化

    @Documented注解是比较好理解的,它是一个标记注解。被该标记注解标记的注解,生成doc文档时,注解是可以被加载到文档中显示的。

    88f12c96796d7b05323b260e6bf60a33.png

    还拿api中过时的Date中的方法来说,在api中显示Date中的getYear方法是这样的。

    f30e22521b4168ab5314dc3db789d2ef.png

    正如你看到的,注解在api中显示了出来,证明该注解是@Documented注解修饰并文档化的。那我们就看看这个注解是否被@Documented修饰吧。

    b2cadf1d92370d8956a971581249b894.png

    然后,我们发现该注解的确是被文档化了。所以在api中才会显示该注解的。如果不信,你可以自己使用javadoc命令来生成一下doc文档,看看被该注解修饰的注解是否存在。

    至于Javadoc文档生成,我在javadoc文档生成一文中有过详细记载,大家可以进行参考,生成doc文档查看。

    6.5 @Inherited

    @Inherited 指定了被修饰的注解修饰程序元素的时候是可以被子类继承的

    首先进入到源码中,我们也可以清楚的知道,该注解也是一个标记注解。而且它也是被文档化的注解。

    5384d45d8c538ee7ff68733d14045292.png

    其次,我们去在自定义注解中,标注上@Inherited注解。

    112cb0af19befc884e39e30f59e39af1.png

    演示@Inherited注解,我需要创建两个类,同时两个类中有一层的继承关系。如下:

    a9f6d75a098336e901c4980365994de8.png

    我们在Person类中标记了@MyAnno注解,由于该注解被@Inherited注解修饰,我们就可以得出继承于Person类的Student类也同样被@MyAnno注解标记了,如果你要获取该注解的值的话,肯定获取的也是父类上注解值的那个"1"。

    七、使用反射机制解析注解

    自定义注解

    package com.mylifes1110.anno;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * @InterfaceName Sign
     * @Description 描述需要执行的类名和方法名
     * @Author Ziph
     * @Date 2020/6/6
     * @Since 1.8
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Sign {
        String methodName();
    
        String className();
    }

    Cat

    package com.mylifes1110.java;
    
    /**
     * @ClassName Cat
     * @Description 描述一只猫的类
     * @Author Ziph
     * @Date 2020/6/6
     * @Since 1.8
     */
    public class Cat {
        /*
         * @Description 描述一只猫吃鱼的方法 
         * @Author Ziph
         * @Date 2020/6/6
         * @Param []
         * @return void
         */
        public void eat() {
            System.out.println("猫吃鱼");
        }
    }

    准备好,上述代码后,我们就可以开始编写使用反射技术来解析注解的测试类。如下:

    首先,我们先通过反射来获取注解中的methodName和className参数。

    package com.mylifes1110.java;
    
    import com.mylifes1110.anno.Sign;
    
    /**
     * @ClassName SignTest
     * @Description 要求创建cat对象并执行其类中eat方法
     * @Author Ziph
     * @Date 2020/6/6
     * @Since 1.8
     */
    @Sign(className = "com.mylifes1110.java.Cat", methodName = "eat")
    public class SignTest {
        public static void main(String[] args) {
            //获取该类的类对象
            Class<SignTest> signTestClass = SignTest.class;
            //获取类对象中的注解对象
            //原理实际上是在内存中生成了一个注解接口的子类实现对象
            Sign sign = signTestClass.getAnnotation(Sign.class);
            //调用注解对象中定义的抽象方法(注解中的属性)来获取返回值
            String className = sign.className();
            String methodName = sign.methodName();
            System.out.println(className);
            System.out.println(methodName);
        }
    }

    此时的打印结果证明我们已经成功获取到了该注解的两个参数。

    9996938bb58ad8fdb148430e2b022857.png

    注意: 获取类对象中的注解对象时,其原理实际上是在内存中生成了一个注解接口的子类实现对象并返回的字符串内容。如下:

    public class SignImpl implements Sign {
        public String methodName() {
            return "eat";
        }
        
        public String className() {
            return "com.mylifes1110.java.Cat";
        }
    }

    继续编写我们后面的代码,代码完整版如下:

    完整版代码

    package com.mylifes1110.java;
    
    import com.mylifes1110.anno.Sign;
    
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    /**
     * @ClassName SignTest
     * @Description 要求创建cat对象并执行其类中eat方法
     * @Author Ziph
     * @Date 2020/6/6
     * @Since 1.8
     */
    @Sign(className = "com.mylifes1110.java.Cat", methodName = "eat")
    public class SignTest {
        public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
            //获取该类的类对象
            Class<SignTest> signTestClass = SignTest.class;
            //获取类对象中的注解对象
            //原理实际上是在内存中生成了一个注解接口的子类实现对象
            Sign sign = signTestClass.getAnnotation(Sign.class);
            //调用注解对象中定义的抽象方法(注解中的属性)来获取返回值
            String className = sign.className();
            String methodName = sign.methodName();
            //获取className名称的类对象
            Class<?> clazz = Class.forName(className);
            //创建对象
            Object o = clazz.newInstance();
            //获取methodName名称的方法对象
            Method method = clazz.getMethod(methodName);
            //执行该方法
            method.invoke(o);
        }
    }

    执行结果

    执行后成功的调用了eat方法,并打印了猫吃鱼的结果,如下:

    8245ed323433c8fe990b98e4c82eada6.png

    ###

    八、自定义注解改变JDBC工具类

    首先,我们在使用JDBC的时候是需要通过properties文件来获取配置JDBC的配置信息的,这次我们通过自定义注解来获取配置信息。其实使用注解并没有用配置文件好,但是我们需要了解这是怎么做的,获取方法也是鱼使用反射机制解析注解,所谓“万变不离其宗”,它就是这样的。

    自定义注解

    package com.mylifes1110.java.anno;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * @InterfaceName DBInfo
     * @Description 给予注解声明周期为运行时并限定注解只能用在类上
     * @Author Ziph
     * @Date 2020/6/6
     * @Since 1.8
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface DBInfo {
        String driver() default "com.mysql.jdbc.Driver";
    
        String url() default "jdbc:mysql://localhost:3306/temp?useUnicode=true&characterEncoding=utf8";
    
        String username() default "root";
    
        String password() default "123456";
    }
    

    数据库连接工具类

    为了代码的健全我也在里面加了properties文件获取连接的方式。
    package com.mylifes1110.java.utils;
    
    import com.mylifes1110.java.anno.DBInfo;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.sql.*;
    import java.util.Properties;
    
    /**
     * @ClassName DBUtils
     * @Description 数据库连接工具类
     * @Author Ziph
     * @Date 2020/6/6
     * @Since 1.8
     */
    @DBInfo()
    public class DBUtils {
        private static final Properties PROPERTIES = new Properties();
        private static String driver;
        private static String url;
        private static String username;
        private static String password;
    
        static {
            Class<DBUtils> dbUtilsClass = DBUtils.class;
            boolean annotationPresent = dbUtilsClass.isAnnotationPresent(DBInfo.class);
            if (annotationPresent) {
                /**
                 * DBUilts类上有DBInfo注解,并获取该注解
                 */
                DBInfo dbInfo = dbUtilsClass.getAnnotation(DBInfo.class);
    //            System.out.println(dbInfo);
                driver = dbInfo.driver();
                url = dbInfo.url();
                username = dbInfo.username();
                password = dbInfo.password();
            } else {
                InputStream inputStream = DBUtils.class.getResourceAsStream("db.properties");
                try {
                    PROPERTIES.load(inputStream);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            try {
                Class.forName(driver);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    
        public static Connection getConnection() {
            try {
                return DriverManager.getConnection(url, username, password);
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
            return null;
        }
    
        public static void closeAll(Connection connection, Statement statement, ResultSet resultSet) {
            try {
                if (resultSet != null) {
                    resultSet.close();
                    resultSet = null;
                }
    
                if (statement != null) {
                    statement.close();
                    statement = null;
                }
                if (connection != null) {
                    connection.close();
                    connection = null;
                }
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }

    测试类

    package com.mylifes1110.java.test;
    
    import com.mylifes1110.java.utils.DBUtils;
    
    import java.sql.Connection;
    
    /**
     * @ClassName GetConnectionDemo
     * @Description 测试连接是否可以获取到
     * @Author Ziph
     * @Date 2020/6/6
     * @Since 1.8
     */
    public class GetConnectionDemo {
        public static void main(String[] args) {
            Connection connection = DBUtils.getConnection();
            System.out.println(connection);
        }
    }

    测试结果

    为了证明获取的连接是由注解的配置信息获取到的连接,我将properties文件中的所有配置信息删除后测试的。

    7de0ee6170be56090c51e0191494b630.png

    九、自定义@MyTest注解实现单元测试

    我不清楚小伙伴们是否了解,Junit单元测试。@Test是单元测试的测试方法上方修饰的注解。此注解的核心原理也是由反射来实现的。如果有小伙伴不知道什么是单元测试或者对自定义@MyTest注解实现单元测试感兴趣的话,可以点进来看看哦!
    展开全文
  • 说明你倒入的项目可能缺少引用的jar包,或者jar包的位置不对,在选定项目上 点击那个Libraries, 如果项目之前有!号的话,一般来说,下面的引入的jar包会有前面有小红叉的,你edit一下错误的,改正...

    说明你倒入的项目可能缺少引用的jar包,或者jar包的位置不对,在选定项目上

    点击那个Libraries,

    如果项目之前有!号的话,一般来说,下面的引入的jar包会有前面有小红叉的,你edit一下错误的,改正一下就可以了。

    选择弹出对话框的“Libraries”

    (此时项目中的jar肯定有错误路径引入,可以直接删除错误的引入),

       之后选择“add External JARs”;


    第三步:找到jar文件的存放路径,选择一个或多个后,

    直接重新引入即可,项目重新编译后,红色叹号会自动消失。



    展开全文
  • 不知道什么是Java注解?莫慌,十分钟一篇文章就能深度学习!所有知识体系文章,GitHub已收录,欢迎Star!再次感谢,愿你早日进入大厂!GitHub地址:https://github.com/ThinkingHan/Java-noteJava注解一、Java注解...

    c9db199e0102f13e4b791370913a3d15.png

    不知道什么是Java注解?莫慌,十分钟一篇文章就能深度学习!

    3cd3aaccba1d0afa5760226bbcb10e6c.png

    所有知识体系文章,GitHub已收录,欢迎Star!再次感谢,愿你早日进入大厂!

    GitHub地址:https://github.com/ThinkingHan/Java-note

    Java注解

    一、Java注解概述

    注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。

    二、注解的作用分类

    • 编写文档: 通过代码里标识的元数据生成文档【生成文档doc文档】
    • 代码分析: 通过代码里标识的元数据对代码进行分析【使用反射】
    • 编译检查: 通过代码里标识的元数据让编译器能够实现基本的编译检查【Override等】

    编写文档

    首先,我们要知道Java中是有三种注释的,分别为单行注释、多行注释和文档注释。而文档注释中,也有@开头的元注解,这就是基于文档注释的注解。我们可以使用javadoc命令来生成doc文档,此时我们文档的内元注解也会生成对应的文档内容。这就是编写文档的作用。

    代码分析

    我们频繁使用之一,也是包括使用反射来通过代码里标识的元数据对代码进行分析的,此内容我们在后续展开讲解。

    编译检查

    至于在编译期间在代码中标识的注解,可以用来做特定的编译检查,它可以在编译期间就检查出“你是否按规定办事”,如果不按照注解规定办事的话,就会在编译期间飘红报错,并予以提示信息。可以就可以为我们代码提供了一种规范制约,避免我们后续在代码中处理太多的代码以及功能的规范。比如,@Override注解是在我们覆盖父类(父接口)方法时出现的,这证明我们覆盖方法是继承于父类(父接口)的方法,如果该方法稍加改变就会报错;@FunctionInterface注解是在编译期检查是否是函数式接口的,如果不遵循它的规范,同样也会报错。

    三、jdk的内置注解

    3.1 内置注解分类

    • @Override: 标记在成员方法上,用于标识当前方法是重写父类(父接口)方法,编译器在对该方法进行编译时会检查是否符合重写规则,如果不符合,编译报错。
    • @Deprecated: 用于标记当前类、成员变量、成员方法或者构造方法过时如果开发者调用了被标记为过时的方法,编译器在编译期进行警告。
    • @SuppressWarnings: 压制警告注解,可放置在类和方法上,该注解的作用是阻止编译器发出某些警告信息。

    3.2 @Override注解

    标记在成员方法上,用于标识当前方法是重写父类(父接口)方法,编译器在对该方法进行编译时会检查是否符合重写规则,如果不符合,编译报错。

    这里解释一下@Override注解,在我们的Object基类中有一个方法是toString方法,我们通常在实体类中去重写此方法来达到打印对象信息的效果,这时候也会发现重写的toString方法上方就有一个@Override注解。如下所示:

    a13cad32eec20ac896702e490a38b349.png

    于是,我们试图去改变重写后的toString方法名称,将方法名改为toStrings。你会发现在编译期就报错了!如下所示:

    37752ac6f6f2f803b254835e1b71a992.png

    那么这说明什么呢?这就说明该方法不是我们重写其父类(Object)的方法。这就是@Override注解的作用。

    3.3 @Deprecated注解

    用于标记当前类、成员变量、成员方法或者构造方法过时如果开发者调用了被标记为过时的方法,编译器在编译期进行警告。

    我们解释@Deprecated注解就需要模拟一种场景了。假设我们公司的产品,目前是V1.0版本,它为用户提供了show1方法的功能。这时候我们为产品的show1方法的功能又进行了扩展,打算发布V2.0版本。但是,我们V1.0版本的产品需要抛弃吗?也就是说我们V1.0的产品功能还继续让用户使用吗?答案肯定是不能抛弃的,因为有一部分用户是一直用V1.0版本的。如果抛弃了该版本会损失很多的用户量,所以我们不能抛弃该版本。这时候,我们对功能进行了扩展后,发布了V2.0版本,我们给予用户的通知就可以了,也就是告知用户我们在V2.0版本中为功能进行了扩展。可以让用户自行选择版本。

    但是,除了发布告知用户版本情况之外,我们还需要在原来版本的功能上给予提示,在上面的模拟场景中我们需要在show1方法上方加@Deprecated注解给予提示。通过这种方式也告知用户“这是旧版本时候的功能了,我们不建议再继续使用旧版本的功能”,这句话的意思也就正是给用户做了提示。用户也会这么想“奥,这版本的这个功能不好用了,肯定有新版本,又更好用的功能。我要去官网查一下下载新版本”,还会有用户这么想“我明白了,又更新出更好的功能了,但是这个版本的功能我已经够用了,不需要重新下载新版本了”。

    1370ca8d7769fc27f7f848dfae137f61.png

    那么我们怎么查看我上述所说的在功能上给予的提示呢?这时候我需要去创建一个方法,然后去调用show1方法,并查看调用时它是如何提示的。

    a9edb6b154d005bef73d1ab89ef2dfd5.png

    图已经贴出来了,你是否发现的新旧版本功能的异同点呢?很明显,在方法中的提示是在调用的方法名上加了一道横线把该方法划掉了。这就体现了show1方法过时了,已经不建议使用了,我们为你提供了更好的。

    回想起来,在我们的api中也会有方法是过时的,比如我们的Date日期类中的方法有很多都已经过时了。如下图:

    85d62f6c216b7af19c614f520205d210.png

    0a84e607a63bcae4522dfb3000729728.png

    如你所见,是不是有很多方法都过时了呢?那它的方法上是加了@Deprecated注解吗?来跟着我的脚步,我带你们看一下。

    ef8684ab7cdf1e32066f370a5ba0f40e.png

    我们已经知道的Date类中的这些方法已经是过时的了,如果我们使用该方法并执行该程序的话。执行的过程中就会提示该方法已过时的内容,但是只是提示,并不影响你使用该方法。如下:

    92245a24508876fe437242fc573831eb.png

    OK!这也就是@Deprecated注解的作用了。

    3.4 @SuppressWarnings注解

    压制警告注解,可放置在类和方法上,该注解的作用是阻止编译器发出某些警告信息,该注解为单值注解,只有 一个value参数,该参数为字符串数组类型,参数值常用的有如下几个。

    • unchecked:未检查的转化,如集合没有指定类型还添加元素
    • unused:未使用的变量
    • resource:有泛型未指定类型
    • path:该类路径,原文件路径中有不存在的路径
    • deprecation:使用了某些不赞成使用的类和方法
    • fallthrough:switch语句执行到底没有break关键字
    • rawtypes:没有写泛型,比如: List list = new ArrayList();
    • all:全部类型的警告

    压制警告注解,顾名思义就是压制警告的出现。我们都知道,在Java代码的编写过程中,是有很多黄色警告出现的。但是我不知道你的导师是否教过你,程序员只需要处理红色的error,不需要理会黄色的warning。如果你的导师说过此问题,那是有原因的。因为在你学习阶段,我们认清处理红色的error即可,这样可以减轻你学习阶段在脑部的记忆内容。如果你刚刚加入学习Java的队列中,需要大脑记忆的东西就有太多了,也就是我们目前不需要额外记忆其他的东西,只记忆重点即可。至于黄色warning嘛,在你的学习过程中慢慢就会有所了解的,而不是死记硬背的。

    那为了解释@SuppressWarnings注解,我们还使用上一个例子,因为在那个例子中就有黄色的warning出现。

    057d6711315d0eacf42d2b79cbbb1f76.png

    而每一个黄色的warning都会有警告信息的。比如,这一个图中的警告信息,就告知你show2()方法没有被使用,简单来说,你创建的show2方法,但是你在代码中并没有调用过此方法。以后你便会遇到各种各样黄色的warning。然后, 我们就可以使用不同的注解参数来压制不同的注解。但是在该注解的参数中,提供了一个all参数可以压制全部类型的警告。而这个注解是需要加到类的上方,并赋予all参数,即可压制所有警告。如下:

    2fdc463a4a77c2bb75bd1c5cf740a313.png

    我们加入注解并赋予all参数后,你会发现use方法和show2方法的警告没有了,实际上导Date包的警告还在,因为我们Date包导入到了该类中,但是我们并没有创建Date对象,也就是并没有写入Date在代码中,你也会发现那一行是灰色的,也就证明了我们没有去使用导入这个包的任何信息的说法,出现这种情况我们就需要把这个没有用的导包内容删除掉,使用Ctrl + X删除导入没有用到的包即可。还有一种办法就是在包的上方修饰压制警告注解,但是我认为在一个没有用的包上加压制注解是毫无意义的,所以,我们直接删除就好。

    然后,我们还见到上图,注解那一行出现了警告信息提示。这一行的意思是冗余的警告压制。这就是说我们压制以下的警告并没有什么意义而造成的冗余,但是如果我们使用了该类并做了点什么的话,压制注解的冗余警告就会消失,毕竟我们使用了该类,此时就不会早场冗余了。

    上述解释@SuppressWarnings注解也差不多就这些了。OK,继续向下看吧。持续为大家讲解。

    3.5 @Repeatable注解

    @Repeatable 表明标记的注解可以多次应用于相同的声明或类型,此注解由Java8版本引入。我们知道注解是不能重复定义的,其实该注解就是一个语法糖,它可以重复多次使用,更适用于我们的特殊场景。

    首先,我们先创建一个可以重复使用的注解。

    package com.mylifes1110.anno;
    
    import java.lang.annotation.Repeatable;
    
    @Repeatable(Hour.class)
    public @interface Hours {
        double[] hours() default 0;
    }

    你会发现注解要求传入的只是一个类对象,此类对象就需要传入另外一个注解,这里也就是另外一个注解容器的类对象。我们去创建一下。

    package com.mylifes1110.anno;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    //容器
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Hour {
        Hours[] value();
    }

    其实,这两个注解的套用,就是将一个普通的注解封装了一个可重复使用的注解,来达到注解的复用性。最后,我们创建一下测试类,随后带你去看一下源码。

    package com.mylifes1110.java;
    
    import com.mylifes1110.anno.Hours;
    
    @Hours(hours = 4)
    @Hours(hours = 4.5)
    @Hours(hours = 2)
    public class Worker {
        public static void main(String[] args) {
            //通过Hours注解类型来获取Worker中的值数组对象
            Hours[] hours = Worker.class.getAnnotationsByType(Hours.class);
            //遍历数组
            for (Hours h : hours) {
                System.out.println(h);
            }
        }
    }

    测试类,是一个工人测试类,该工人使用注解记录早中晚的工作时间。测试结果如下:

    0005d373e5e7d91c6af8abbe7c01642f.png

    然后我们进入到源码一探究竟。

    2afeacc75e5a08ee671273e0ad2e7b72.png

    我们发现进入到源码后,就只看见一个返回值为类对象的抽象方法。这也就验证了该注解只是一个可实现重复性注解的语法糖而已。

    四、注解分类

    4.1 注解分类

    注解可以根据注解参数分为三大类:

    • 标记注解: 没有参数的注解,仅用自身的存在与否为程序提供信息,如@Override注解,该注解没有参数,用于表示当前方法为重写方法。
    • 单值注解: 只有一个参数的注解,如果该参数的名字为value,那么可以省略参数名,如 @SuppressWarnings(value = “all”),可以简写为@SuppressWarnings(“all”)。
    • 完整注解: 有多个参数的注解。

    4.2 标记注解

    说到@Override注解是一个标记注解,那我们进入到该注解的源码查看一下。从上往下看该注解源码,发现它继承了导入了java.lang.annotation.*,也就是有使用到该包的内容。然后下面就又是两个看不懂的注解,其实发现注解的定义格式是public修饰的@Interface,最终看到该注解中方法体并没有任何参数,也就是只起到标记作用。

    22cfe3e0ab47f7adf81d15bedb5a55b8.png

    4.3 单值注解

    在上面我们用到的@SuppressWarnings注解就是一个单值注解。那我们进入到它的源码看一下是怎么个情况。其实,和标记注解比较,它就多一个value参数而已,而这就是单值注解的必要条件,即只有一个参数。并且这一个参数为value时,我们可以省略value。

    f57bdb2e5f84da31bc7ddb049d3ce1a5.png

    4.4 完整注解

    上述两个类型注解讲解完,至于完整注解嘛,这下就能更明白了。其中的方法体就是有多个参数而已。

    五、自定义注解

    5.1 自定义注解格式

    格式: public @Interface 注解名 {属性列表/无属性}注意: 如果注解体中无任何属性,其本质就是标记注解。但是与其标注注解还少了上边修饰的元注解。

    如下,这就是一个注解。但是它与jdk自定义注解有点区别,jdk自定义注解的上方还有注解来修饰该注解,而那注解就叫做元注解。元注解我会在后面详细的说到。

    5fe733f99ed3953cccbb13487d8d5ddc.png

    这里我们的确不知道@Interface是什么,那我们就把自定义的这个注解反编译一下,看一下反编译信息。反编译操作如下:

    71d1a4547c9f2369b4c2a34cee9fc6e5.png

    反编译后的反编译内容如下:

    public interface com.mylifes1110.anno.MyAnno extends java.lang.annotation.Annotation {
    }

    首先,看过反编译内容后,我们可以直观的得知它是一个接口,因为它的public修饰符后面的关键字是interface。

    其次,我们发现MyAnno这个接口是继承了java.lang.annotation包下的Annotation接口。

    所以,我们可以得知注解的本质就是一个接口,该接口默认继承了Annotation接口。

    既然,是继承的Annotation接口,那我们就去进入到这个接口中,看它定义了什么。以下是我抽取出来的接口内容。我们发现它看似很常见,其实它们不是很常用,作为了解即可。

    public interface Annotation {
        boolean equals(Object obj);
        int hashCode();
        String toString();
    	Class<? extends Annotation> annotationType();
    }

    最后,我们的注解中也是可以写有属性的,它的属性不同于普通的属性,它的属性是抽象方法。既然注解也是一个接口,那么我们可以说接口体中可以定义什么,它同样也可以定义,而它的修饰符与接口一样,也是默认被public abstract修饰。

    而注解体中的属性也是有要求的。其属性要求如下:

    属性的返回值类型必须是以下几种:

    • 基本数据类型
    • String类型
    • 枚举类型
    • 注解
    • 以上类型的数组
    • 注意: 在这里不能有void的无返回值类型和以上类型以外的类型

    定义的属性,在使用时需要给注解中的属性赋值

    • 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时可以不为属性赋值,它取的是默认值。如果为它再次传入值,那么就发生了对原值的覆盖。
    • 如果只有一个属性需要赋值,并且属性的名称为value,则赋值时value可以省略,可以直接定义值
    • 数组赋值时,值使用{}存储值。如果数组中只有一个值,则可以省略{}。

    5.2 自定义注解属性的返回值

    属性返回值既然有以上几种,那么我就在这里写出这几种演示一下是如何写的。

    首先,定义一个枚举类和另外一个注解备用。

    package com.mylifes1110.enums;
    
    public enum Lamp {
        RED, GREEN, YELLOW
    }
    
    package com.mylifes1110.anno;
    
    public @interface MyAnno2 {
    }

    其次,我们来定义上述几种类型,如下:

    package com.mylifes1110.anno;
    
    import com.mylifes1110.enums.Lamp;
    
    public @interface MyAnno {
        //基本数据类型
        int num();
    
        //String类型
        String value();
    
        //枚举类型
        Lamp lamp();
    
        //注解类型
        MyAnno2 myAnno2();
        
        //以上类型的数组
        String[] values();
        Lamp[] lamps();
        MyAnno2[] myAnno2s();
        int[] nums();
    }

    5.3 自定义注解的属性赋值

    这里我们演示一下,首先,我们使用该注解来进行演示。

    package com.mylifes1110.anno;
    
    public @interface MyAnno {
        //基本数据类型
        int num();
    
        //String类型
        String value();
    }

    随后创建一个测试类,在类的上方写上注解,你会发现,注解的参数中会让你写这两个参数(int、String)。

    49bad2a11714a7f905bcc132fa89afbf.png

    此时,传参是这样来做的。格式为:名称 = 返回值类型参数。如下:

    2fb2bc8e0ee929533308f2efa3b9d6ea.png

    上述所说,如果使用default关键字给属性默认初始化值,就不需要为其参数赋值,如果赋值的话,就把默认初始化的值覆盖掉了。

    81e8b7ffdcc4dfabef9dbff56dd21ab2.png

    当然还有一个规则,如果只有一个属性需要赋值,并且属性的名称为value,则赋值时value可以省略,可以直接定义值。那么,我们的num已经有了默认值,就可以不为它传值。我们发现,注解中定义的属性就剩下了一个value属性值,那么我们就可以来演示这个规则了。

    8b12e42dc41845169188a359b2f188e7.png

    这里,我并没有写属性名称value,而是直接为value赋值。如果我将num的default关键字修饰去掉呢,那意思也就是说在使用该注解时必须为num赋值,这样可以省略value吗?那我们看一下。

    7c8f46f5e3c424f2800ea8987ae1d258.png

    结果,就是我们所想的,它报错了,必须让我们给num赋值。其实想想这个规则也是很容易懂的,定义一个为value的值,就可以省略其value名称。如果定义多个值,它们可以省略名称就无法区分定义的是哪个值了,关键是还有数组,数组内定义的是多个值呢,对吧。

    5.4 自定义注解的多种返回值类型赋值

    这里我们演示一下,上述的多种返回值类型是如何赋值的。这里我们定义这几个参数来看一下,是如何为属性赋值的。

    ef5fcf9572b8a124d13278e1d9bd0d4b.png

    num是一个int基本数据类型,即num = 1

    value是一个String类型,即value = "str"

    lamp是一个枚举类型,即lamp = Lamp.RED

    myAnno2是一个注解类型,即myAnno2 = @MyAnno2

    values是一个String类型数组,即values = {"s1", "s2", "s3"}

    values是一个String类型数组,其数组中只有一个值,即values = "s4"

    注意: 值与值之间是,隔开的;数组是用{}来存储值的,如果数组中只有一个值可以省略{};枚举类型是枚举名.枚举值

    193799efc5de613af8c7285bbf23e1c1.png

    六、元注解

    6.1 元注解分类

    元注解就是用来描述注解的注解。一般使用元注解来限制自定义注解的使用范围、生命周期等等。

    而在jdk的中java.lang.annotation包中定义了四个元注解,如下:

    74eb5806238540703ffe9c35880551a4.png

    6.2 @Target

    @Target 指定被修饰的注解的作用范围。其作用范围可以在源码中找到参数值。

    fcf61db8a738aeba43afe0462f12c466.png

    由此可见,该注解体内只有一个value属性值,但是它的类型是一个ElementType数组。那我们进入到这个数组中继续查看。

    b9dd38c7a2c71cbb4cd57c203f405e42.png

    进入到该数组中,你会发现它是一个枚举类,其中定义了上述表格中的各个属性。

    fb8948f0daf82b87d836a43fd40602ba.png

    了解了@Target的作用和属性值后,我们来使用一下该注解。首先,我们要先用该注解来修饰一个自定义注解,定义该注解的指定作用在类上。如下:

    7cce9de1b524575bc1ae0c12baa12ee9.png

    而你观察如下测试类,我们把注解作用在类上时是没有错误的。而当我们的注解作用在其他地方就会报错。这也就说明了,我们@Target的属性起了作用。

    注意: 如果我们定义多个作用范围时,也是可以省略该参数名称了,因为该类型是一个数组,虽然能省略名称但是,我们还需要用{}来存储。

    9c25b92a5d1a767af2ea84f4a1598001.png

    6.3 @Retention

    @Retention 指定了被修饰的注解的生命周期

    ce42959cbdf7b33289828efb1c4dff13.png

    注意: 我们常用的定义即是RetentionPolicy.RUNTIME,因为我们使用反射来实现的时候是需要从JVM中获取class类对象并操作类对象的。

    首先,我们要了解反射的三个生命周期阶段,这部分内容我在Java反射机制中也是做了非常详细的说明,有兴趣的小伙伴可以去看看我写的Java反射机制,相信你在其中也会有所收获。

    这里我再次强调一下这三个生命周期是源码阶段 - > class类对象阶段 - > Runtime运行时阶段。

    那我们进入到源码,看看@Retention注解中是否有这些参数。

    9de97d57f87f116ae27e0937e9f20757.png

    我们看到该注解中的属性只有一个value,而它的类型是一个RetentionPolicy类型,我们进入到该类型中看看有什么参数,是否与表格中的参数相同呢?

    710bb01384d3030b2e9458be6e718aa8.png

    至于该注解怎么使用,其实是相同的,用法如下:

    b5c7e09a2e5c108744fb7cf1953a2f61.png

    这就证明了我们的注解可以保留到Runtime运行阶段,而我们在反射中大多数是定义到Runtime运行时阶段的,因为我们需要从JVM中获取class类对象并操作类对象。

    6.4 @Documented

    @Documented 指定了被修饰的注解是可以Javadoc等工具文档化

    @Documented注解是比较好理解的,它是一个标记注解。被该标记注解标记的注解,生成doc文档时,注解是可以被加载到文档中显示的。

    234599c4a8fd1f01665254516b1c989d.png

    还拿api中过时的Date中的方法来说,在api中显示Date中的getYear方法是这样的。

    1c0344cd3719152bb6d377afa9625609.png

    正如你看到的,注解在api中显示了出来,证明该注解是@Documented注解修饰并文档化的。那我们就看看这个注解是否被@Documented修饰吧。

    7466429ac3d5ce573ce5d2fec2e55c4b.png

    然后,我们发现该注解的确是被文档化了。所以在api中才会显示该注解的。如果不信,你可以自己使用javadoc命令来生成一下doc文档,看看被该注解修饰的注解是否存在。

    6.5 @Inherited

    @Inherited 指定了被修饰的注解修饰程序元素的时候是可以被子类继承的

    首先进入到源码中,我们也可以清楚的知道,该注解也是一个标记注解。而且它也是被文档化的注解。

    8481491d3fa75fcac5a330b1f6621451.png

    其次,我们去在自定义注解中,标注上@Inherited注解。

    af1a5398cc8a67f2562c04a61d496e64.png

    演示@Inherited注解,我需要创建两个类,同时两个类中有一层的继承关系。如下:

    84e205f494664657f9a514179989d9aa.png

    我们在Person类中标记了@MyAnno注解,由于该注解被@Inherited注解修饰,我们就可以得出继承于Person类的Student类也同样被@MyAnno注解标记了,如果你要获取该注解的值的话,肯定获取的也是父类上注解值的那个"1"。

    七、使用反射机制解析注解

    自定义注解

    package com.mylifes1110.anno;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * @InterfaceName Sign
     * @Description 描述需要执行的类名和方法名
     * @Author Ziph
     * @Date 2020/6/6
     * @Since 1.8
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Sign {
        String methodName();
    
        String className();
    }

    Cat

    package com.mylifes1110.java;
    
    /**
     * @ClassName Cat
     * @Description 描述一只猫的类
     * @Author Ziph
     * @Date 2020/6/6
     * @Since 1.8
     */
    public class Cat {
        /*
         * @Description 描述一只猫吃鱼的方法 
         * @Author Ziph
         * @Date 2020/6/6
         * @Param []
         * @return void
         */
        public void eat() {
            System.out.println("猫吃鱼");
        }
    }

    准备好,上述代码后,我们就可以开始编写使用反射技术来解析注解的测试类。如下:

    首先,我们先通过反射来获取注解中的methodName和className参数。

    package com.mylifes1110.java;
    
    import com.mylifes1110.anno.Sign;
    
    /**
     * @ClassName SignTest
     * @Description 要求创建cat对象并执行其类中eat方法
     * @Author Ziph
     * @Date 2020/6/6
     * @Since 1.8
     */
    @Sign(className = "com.mylifes1110.java.Cat", methodName = "eat")
    public class SignTest {
        public static void main(String[] args) {
            //获取该类的类对象
            Class<SignTest> signTestClass = SignTest.class;
            //获取类对象中的注解对象
            //原理实际上是在内存中生成了一个注解接口的子类实现对象
            Sign sign = signTestClass.getAnnotation(Sign.class);
            //调用注解对象中定义的抽象方法(注解中的属性)来获取返回值
            String className = sign.className();
            String methodName = sign.methodName();
            System.out.println(className);
            System.out.println(methodName);
        }
    }

    此时的打印结果证明我们已经成功获取到了该注解的两个参数。

    9e1332c888d8e626e1e48c83c23bb61b.png

    注意: 获取类对象中的注解对象时,其原理实际上是在内存中生成了一个注解接口的子类实现对象并返回的字符串内容。如下:

    public class SignImpl implements Sign {
        public String methodName() {
            return "eat";
        }
        
        public String className() {
            return "com.mylifes1110.java.Cat";
        }
    }

    继续编写我们后面的代码,代码完整版如下:

    完整版代码

    package com.mylifes1110.java;
    
    import com.mylifes1110.anno.Sign;
    
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    /**
     * @ClassName SignTest
     * @Description 要求创建cat对象并执行其类中eat方法
     * @Author Ziph
     * @Date 2020/6/6
     * @Since 1.8
     */
    @Sign(className = "com.mylifes1110.java.Cat", methodName = "eat")
    public class SignTest {
        public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
            //获取该类的类对象
            Class<SignTest> signTestClass = SignTest.class;
            //获取类对象中的注解对象
            //原理实际上是在内存中生成了一个注解接口的子类实现对象
            Sign sign = signTestClass.getAnnotation(Sign.class);
            //调用注解对象中定义的抽象方法(注解中的属性)来获取返回值
            String className = sign.className();
            String methodName = sign.methodName();
            //获取className名称的类对象
            Class<?> clazz = Class.forName(className);
            //创建对象
            Object o = clazz.newInstance();
            //获取methodName名称的方法对象
            Method method = clazz.getMethod(methodName);
            //执行该方法
            method.invoke(o);
        }
    }

    执行结果

    执行后成功的调用了eat方法,并打印了猫吃鱼的结果,如下:

    b0ef007c275f8ebe84f63e6093dd8bb9.png

    八、自定义注解改变JDBC工具类

    首先,我们在使用JDBC的时候是需要通过properties文件来获取配置JDBC的配置信息的,这次我们通过自定义注解来获取配置信息。其实使用注解并没有用配置文件好,但是我们需要了解这是怎么做的,获取方法也是鱼使用反射机制解析注解,所谓“万变不离其宗”,它就是这样的。

    自定义注解

    package com.mylifes1110.java.anno;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * @InterfaceName DBInfo
     * @Description 给予注解声明周期为运行时并限定注解只能用在类上
     * @Author Ziph
     * @Date 2020/6/6
     * @Since 1.8
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface DBInfo {
        String driver() default "com.mysql.jdbc.Driver";
    
        String url() default "jdbc:mysql://localhost:3306/temp?useUnicode=true&characterEncoding=utf8";
    
        String username() default "root";
    
        String password() default "123456";
    }

    数据库连接工具类

    为了代码的健全我也在里面加了properties文件获取连接的方式。
    package com.mylifes1110.java.utils;
    
    import com.mylifes1110.java.anno.DBInfo;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.sql.*;
    import java.util.Properties;
    
    /**
     * @ClassName DBUtils
     * @Description 数据库连接工具类
     * @Author Ziph
     * @Date 2020/6/6
     * @Since 1.8
     */
    @DBInfo()
    public class DBUtils {
        private static final Properties PROPERTIES = new Properties();
        private static String driver;
        private static String url;
        private static String username;
        private static String password;
    
        static {
            Class<DBUtils> dbUtilsClass = DBUtils.class;
            boolean annotationPresent = dbUtilsClass.isAnnotationPresent(DBInfo.class);
            if (annotationPresent) {
                /**
                 * DBUilts类上有DBInfo注解,并获取该注解
                 */
                DBInfo dbInfo = dbUtilsClass.getAnnotation(DBInfo.class);
    //            System.out.println(dbInfo);
                driver = dbInfo.driver();
                url = dbInfo.url();
                username = dbInfo.username();
                password = dbInfo.password();
            } else {
                InputStream inputStream = DBUtils.class.getResourceAsStream("db.properties");
                try {
                    PROPERTIES.load(inputStream);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            try {
                Class.forName(driver);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    
        public static Connection getConnection() {
            try {
                return DriverManager.getConnection(url, username, password);
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
            return null;
        }
    
        public static void closeAll(Connection connection, Statement statement, ResultSet resultSet) {
            try {
                if (resultSet != null) {
                    resultSet.close();
                    resultSet = null;
                }
    
                if (statement != null) {
                    statement.close();
                    statement = null;
                }
                if (connection != null) {
                    connection.close();
                    connection = null;
                }
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }

    测试类

    package com.mylifes1110.java.test;
    
    import com.mylifes1110.java.utils.DBUtils;
    
    import java.sql.Connection;
    
    /**
     * @ClassName GetConnectionDemo
     * @Description 测试连接是否可以获取到
     * @Author Ziph
     * @Date 2020/6/6
     * @Since 1.8
     */
    public class GetConnectionDemo {
        public static void main(String[] args) {
            Connection connection = DBUtils.getConnection();
            System.out.println(connection);
        }
    }

    测试结果

    为了证明获取的连接是由注解的配置信息获取到的连接,我将properties文件中的所有配置信息删除后测试的。

    761c5bcf773eef04dcfacddec0701a22.png

    九、自定义@MyTest注解实现单元测试

    我不清楚小伙伴们是否了解,Junit单元测试。@Test是单元测试的测试方法上方修饰的注解。此注解的核心原理也是由反射来实现的。
    作者:Ziph
    原文链接:https://blog.csdn.net/weixin_44170221/article/details/106590823
    展开全文
  • ![图片说明](https://img-ask.csdn.net/upload/201502/01/1422758400_275042.png) 引入了插件的样式的写法 为神马会有这么长的class名
  • 1、简单说明 这叫引入kernel32.dll这个动态连接库(顾名思义就是一个链接库)。这个动态连接库里面包含了很多WindowsAPI函数(Application Programming Interface,简称 API 函数。WIN32 API也就是Microsoft W...
  • basestring()说明:basestringstr和unicode的超类(父类),也抽象类,因此不能被调用和实例化,但可以被用来判断一个对象是否为str或者unicode的实例,isinstance(obj, basestring)等价于isinstance(obj, (str, ...
  • composer版本号composer中 ~ ^ 这都是什么意思?require guzzlehttp/guzzle ~1.1.*版本库声明方式:1、确切的版本号 1.0.22、范围 >=1.0 、 >=1.0,<1.2 、>=1.0,<1.1|>1.3 通过使用比较运算符号...
  • JDK1.5及以后版本引入的一个特性,与类、接口、枚举在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。注解的作用:编写文档:通过代码里标识的元...
  • 我的意思是我的引用实际上是可以的,但是在eclipse里面却报错了。。不是什么引用路径出错了。。。这个项目用了很久了怎么可能还有路径出错的问题。。 ![图片说明]...
  • 另外globalCoord是什么意思? 完整代码如下: ``` <!DOCTYPE html> <title>EChartsTest <!-- 引入 echarts.js --> <script src="https://cdn.bootcss.com/echarts/4.2.1-rc1/echarts.min.js">...
  • 秒懂,Java 注解 (Annotation)你可以这样学

    万次阅读 多人点赞 2017-06-27 21:48:30
    至于为什么放这张图,自然为本篇博文服务,接下来我自会说明。好了,可以开始今天的博文了。 Annotation 中文译过来就是注解、标释的意思,在 Java 中注解一个很重要的知识点,但经常还是有点让新手不容易...
  • 首先说明,Ajax InPage Resource Locator这个词我自造的,主要的意思就是在QueryString里添加一部分用于指明在Ajax页面里某项内容的具体位置(也可以认为用户在页面进行的操作的记录)。为什么引入这么一个...
  • 基于‘BOSS直聘招聘信息’分析企业到底需要什么样的PHPer前两篇文章都没看,好意思直接看结果...以下文字的说明,都基于2017-12-14为止的数千条数据为基础来表述的,因为数据动态变化的,或者在后续引入其他网站的
  • 依赖注入(依赖反转)意思是由函数决定要引入什么样的依赖: let mod = angular.module('test',[]); mod.controller('test_c',function($scope,$interval){ //这里就引入两个依赖$scope和$interval }) //...
  • 大家潜意识中认为指数点、涨跌幅越高越好,至于各指数代表的是什么意思并不清楚,但是对于刚接触投资理财的朋友,只有搞懂各指数的含义才能更好地进行投资,尤其进行被动的指数基金投资,接下来我将对国内外金融...
  • php高级开发教程说明

    2008-11-27 11:39:22
    什么意思?)在此处讨论中并不重要。重要的:形式分析的结果越好,逻辑分析就越容易、 越快、越好。 逻辑分析能补偿形式分析中失去的信息,但仅仅在一个有限的程度上补偿。 你也许能读懂前面的这个句子,但要...
  • 说起链表,第一反应:链表一种数据类型!它可以用来存储同种类型多个...什么意思呢?就是说你想在程序中用数组,就要先定义数组,写一个说明语句放在执行语句之前。我们的变量都先定义后使用的嘛,在这个说明语句的
  • 说起链表,第一反应:链表一种数据类型!它可以用来存储同种类型多个批量...什么意思呢?就是说你想在程序中用数组,就要先定义数组,写一个说明语句放在执行语句之前。我们的变量都先定义后使用的嘛,在这个说明.
  • 这块内容网上挺多,所以为了不冲突,改成用小白文描述,是个人都要能看懂的文字,最后总结成一幅图加深理解。 内核帧缓冲区做的–显示刷新机制首先,4.1...这幅图要说明的是引入了VSync时间,这玩意相当于中断,意思是
  • 内部的双向链表到底是什么意思,prev和next到底是什么,为什么要引入heap和tail来值向null的Node节点。高并发时候是如何保证state来记录重入锁的,在我们的上次博客都做了详细的说明。这次我们来聊一些简单易懂且...
  • 内部的双向链表到底是什么意思,prev和next到底是什么,为什么要引入heap和tail来值向null的Node节点。高并发时候是如何保证state来记录重入锁的,在我们的上次博客都做了详细的说明。这次我们来聊一些简单易懂且...
  • 技术人员的喜好往往是什么技术流行就追什么技术。现在的技术发展快,前后端不断涌现各种框架,我们恨不得把这些框架都用在自己的项目里才行,要不然怎么好意思和别人打招呼啊。 我亲身经历,...
  • 引入:int(5)和char(5)或者varchar(5)中的数字指的是什么意思字节数,还是字符长度?为什么在整型中指定了int(5)却可以输入123456?答案后者,不管整型还是字符串类型,后面跟的数字都字符长度,即字符的...
  • Vue-cli搭建SPA项目

    2020-08-19 10:05:34
    vue项目结构说明vue-cli搭建SPA综合案例vue中import引入模块路径中@符号是什么意思?cmd安装运行命令一问一答截图cmd项目启动截图 前提:搭建好NodeJS环境并先运行命令查看环境是否有问题. node -v npm -v 什么vue...
  •  int(5)和char(5)或者varchar(5)中的数字指的是什么意思字节数,还是字符长度?为什么在整型中指定了int(5)却可以输入123456?  答案后者,不管整型还是字符串类型,后面跟的数字都字符长度,即字符的...

空空如也

空空如也

1 2 3 4 5 6
收藏数 106
精华内容 42
关键字:

引入说明什么意思是什么