• 《回炉重造》——注解


    注解

    前言

    以前学习到「注解」的时候,没有好好理解注解是如何工作的,只是知道注解可以实现一些功能,总而言之,就是懵懵懂懂。

    不过,即使你不知道什么是注解,但肯定接触过注解,比如方法的重写,在方法上面写着 @Override,这个东西就是注解。

    好了,下面就开始回炉重造!打好基础!

    注解

    什么是注解?

    注解(Annotation),Annotation 的意思有「标注、批注、附注」。

    注解和泛型一样,也是从 JDK5 引入的新特性。它是与类、接口处在同一级别的,有它的语法来定义注解的

    注解,人们还有另一个词来称呼它,即「元数据」,所谓元数据(metadata),就是用来描述数据的数据(data about data)。初看这句话可能会有点懵,不过没关系,都是这样过来的,好好细品细品。

    品不过来?可以看看这里:

    什么是元数据?为何需要元数据? - 贺易之的回答 - 知乎

    元数据(MetaData)- 阮一峰

    好了,我们知道「注解==元数据」,那么它描述什么数据呢?问得好,描述的就是我们写的源代码!可以描述类、方法、局部变量、方法参数等数据(这些数据也有另一个叫法,Java Element)。注意上面我说过 Annotation 的意思有 附注 的意思,是吧,所以说这个注解实际上并不是程序本身,只是向编译器提供相关的程序的附加信息,也就是说注解并不会影响程序的运行。

    为什么会有注解的出现?

    这就扯到历史的发展了,综合我所了解的,大概就是这样子:

    这里也扯到了 XML(Extensible Markup Language),如果不熟悉,我这里就简单说下,熟悉可以跳过啦!

    XML 翻译过来就是「可扩展的标记语言」,与 HTML 类似。但是 XML 是被设计用来传输数据的,并不是显示数据,具有「自我描述性」。

    下面是 Jayson Tatum 写给 Kobe Bryant 的短信,存储为 XML:

    复制代码
    • 1
    • 2
    • 3
    • 4
    • 5
    xml
    <text-message> <to>Kobe Bryant</to> <from>Jayson Tatum</from> <body>I got you today</body> </text-message>

    上面的短信就具有自我描述性。它拥有留言,同时包含了发送者和接受者的信息。当然,这仅仅是描述而已,XML 并没有做任何事情,就是纯文本,我们需要写代码,让代码识别这些标签,赋予意义,这样程序才能读懂 XML,知道它描述的是什么。

    目前 XML 有两个作用,一是可以用 XML 格式来传输数据,二是可以作为配置文件。

    在注解出现之前,开发者基本是用 XML 来配置某些东西,因为 XML 和代码是低耦合的,符合低耦合的需求,但是,随着从 XML 描述的数据越来越多,配置越来越复杂,人们发现用 XML 描述数据太复杂了,需要一种高耦合的来降低这种复杂的情况,所以才出现注解,用注解来描述数据。

    不过,现在也是看情况,并不是说注解代替了 XML,而是有时使用 XML,有时使用注解,各有各的好处,两者相互结合等,这还需要具体情况具体分析。

    为什么需要学习注解?

    1. 很显然,我们现在日常开发,使用到各种开源框架,经常会用到注解,不学习注解,那自然看不懂注解相关的代码。
    2. 看起来比较厉害

    JDK 自带的注解

    • @Override :表示当前方法覆盖了父类的方法

    • @Deprecated: 表示该方法由于安全、性能问题等,已经不推荐使用了,即已经过时,方法上有横线,使用时会有警告。此外,在版本升级时,如果要计划删除一些方法,也通常会在前一个版本中,将该方法加上@Deprecated,然后再在后续版本中删除。

    • @SuppviseWarnings(value="unchecked"): 表示镇压警告信息(让编译器忽略特定的编译警告)

      一些 value 值:

      • uncheckeddeprecation(忽略 一些过期的 API 警告)
      • unused(忽略 没有被使用过的代码的警告)
      • fallthrough( 忽略 switch 中缺失 break 的警告)
      • all(忽略全部)

    在 JDK 7 和 8,分别加入了 @SafeVarargs@FunctionalInterface 这两个注解。这两个后续再来填坑吧。

    @Override有什么用?不加行不行?

    @Override 是我们最早接触的注解了,不加它行不行?

    行!不加也行!只要重写了方法正确就可以了,要是写错了,举个例子:

    复制代码
    • 1
    • 2
    • 3
    • 4
    • 5
    java
    public class Pet { public void eat() { System.out.println("吃..."); } }
    复制代码
    • 1
    • 2
    • 3
    • 4
    • 5
    java
    public class Cat extends Pet{ public void aet() { System.out.println("吃鱼..."); } }

    这里 Cat 继承了 Pet,重写 eat() 方法,但是方法名写错了,写成 aet()

    复制代码
    • 1
    • 2
    java
    Pet pet = new Cat(); pet.eat(); // 父类引用调用子类方法

    由于子类重写的方法写错方法名,那么此时父类引用调用子类方法时,就找不到子类的 eat() 这个方法,然后就只能回去父类找,这显然不是我们想要的。

    所以加上 @Override 有好处,写错方法名会有提示,也就是说,可以确保重写的方法,的确存在于父类/接口中,可以有效的避免单词拼错等情况。

    注解的分类

    有两种分类方式:

    • 按照运行机制分类
    • 按照来源分类

    注解的分类

    按照运行机制分类(何时保留,何时不保留,某个时期保留,某个时期不保留,生命周期,Retention)

    • SOURCE——源码注解:注解只在源码中存在,编译成.class文件就不存在了

    • CLASS——编译时注解:注解在源码和 .class文件 中都存在(如:JDK 自带注解)

    • RUNTIME——运行时注解:在运行阶段还起作用,甚至会影响运行逻辑的注解(如:Spring中@Autowired

    按照来源分类

    • 元注解:给注解进行注解,也就是来修饰注解的,有4个;这里的元注解就好比上面说过的元数据,如果还是不能很好的理解的话,就把它理解成形容词

      • @Target({ElementType.METHOD, ElementType.TYPE})
      • @Retention(RetentionPolicy.RUNTIME)
      • @Inherited
      • @Documented

      @Target:用于声明注解的作用域,可以是

      • ElementType.CONSTRUCTOR 可作用于构造方法
      • ElementType.FIELD 字段声明
      • ElementType.LOCAL_VARIABLE 局部变量声明
      • ElementType.METHOD 方法声明
      • ElementType.PACKAGE 包声明
      • ElementType.PARAMETER 参数声明
      • ElementType.TYPE 类、接口、枚举、注解声明。

      @Retention:用于声明注解何时保留,也有人们称为生命周期,可以是

      • RetentionPolicy.SOURCE 只在源码显示,编译时会丢弃
      • RetentionPolicy.CLASS 编译时会记录到class中,运行时忽略
      • RetentionPolicy.RUNTIME 运行时存在,可以通过反射读取。

      @Inherited:允许子类继承,即声明该注解会被使用了该注解的类的子类所继承。

      @Documented:使用了这个注解,那么在生成 Javadoc 的时候会包含注解信息。

      所以,使用这4个元注解,就是用来修饰我们自定义的注解的,规定我们自定义的注解在哪些地方能用,什么时候会保留注解信息等等。

      在 JDK 8 新加了一个元注解 @Repeatable,这个也后续再来填坑啦。

    • JDK 自带的注解

    • 常见第三方注解(Spring、MyBatis等等)

    • 自定义注解

    如何自定义注解

    使用 @interface 关键字进行注解的自定义,写出你自己定义的注解~

    复制代码
    • 1
    • 2
    • 3
    • 4
    java
    public @interface 注解名 { 成员变量...以无参数无异常的方式来声明 可以用defalt关键字来指定默认值 }

    举个例子:

    复制代码
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    java
    @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface TextMessage { // 使用 @interface 关键字定义注解,注解名为 TextMessage String to(); // 成员变量以无参数无异常的方式来声明 String from() default "Jayson Tatum"; // 可以用defalt关键字来指定默认值 String body(); }

    注意事项:

    • 注解只有成员变量,没有方法,虽然这个变量有小括号,看起来挺像方法的。
    • 注解中定义成员变量时它的类型只能是 8 种基本数据类型外加 String 、类、接口、注解、枚举及它们的数组。
    • 若注解只有一个成员变量,则该成员名必须取名为 value() ,然后使用注解的时候参数直接写。
    复制代码
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    java
    @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface A { String value(); } public class B { @A("god23bin") public int var; }
    • 注解可以没有成员变量,这样的注解称为标识注解。

    获取注解信息

    还记得开头我说过的话吗?

    注解实际上并不是程序本身,只是向编译器提供相关的程序的附加信息,也就是说注解并不会影响程序的运行。

    是的,注解这样子,根本不能帮我们做什么事情,顶多是给我们人看的,我们知道它描述什么,此时它就和注释的功能很像了,但是!我们知道,注解不仅仅是给我们人看的,也是给机器看的啊,那么机器是如何看的呢?这就涉及到反射了,需要同个反射去获取注解的信息,进而进行下一步的操作,实现我们想要的功能!

    举个例子:

    我这里自定义了一个注解 B,作用域为类、接口、枚举、注解、方法、字段(成员变量)。

    注解的保留时期(Retention)是运行时(RetentionPolicy.RUNTIME),这是必须的,毕竟反射是运行时动态获取的,不选这个,就不能使用反射获取注解信息了。

    该注解有两个成员变量,或者说两个属性,即 idtextid 默认 -1

    复制代码
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    java
    @Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface B { int id() default "-1"; String text(); }

    现在,我们把这个注解声明到 Test 类上:

    复制代码
    • 1
    • 2
    • 3
    • 4
    java
    @B() public class Test { }

    那如何使用反射获取呢?我们可以通过反射中的 Class 对象isAnnotationPresent() 方法判断该类是否使用了某个注解。

    复制代码
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    java
    /** * {@inheritDoc} * @throws NullPointerException {@inheritDoc} * @since 1.5 */ @Override public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) { return GenericDeclaration.super.isAnnotationPresent(annotationClass); }

    然后通过它的 getAnnotation() 方法来获取 Annotation 对象

    复制代码
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    java
    /** * @throws NullPointerException {@inheritDoc} * @since 1.5 */ @SuppressWarnings("unchecked") public <A extends Annotation> A getAnnotation(Class<A> annotationClass) { Objects.requireNonNull(annotationClass); return (A) annotationData().annotations.get(annotationClass); }

    或者是 getAnnotations() 方法来获取 Annotation 数组对象

    复制代码
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    java
    /** * @since 1.5 */ public Annotation[] getAnnotations() { return AnnotationParser.toArray(annotationData().annotations); }

    所以,我们可以这样获取注解信息:

    复制代码
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    java
    @B("god23bin") public class Test { public static void main(String[] args) { boolean isB = Test.class.isAnnotationPresent(B.class); if (isB) { B b = Test.class.getAnnotation(B.class); System.out.println("id:" + b.id()); System.out.println("text:" + b.text()); } } }

    输出结果:

    复制代码
    • 1
    • 2
    er-hljs
    id:-1 text:god23bin

    同理,位于接口、属性(字段)、方法上的注解,同样可以通过反射获取。

    复制代码
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    java
    @B(text="I got you today") public class Test { @B(id=1, text="hello") public int var; @B(text="world") public void empty() { } public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException { boolean isB = Test.class.isAnnotationPresent(B.class); if (isB) { // 获取类上的注解 B b1 = Test.class.getAnnotation(B.class); System.out.println("1-id:" + b1.id()); System.out.println("1-text:" + b1.text()); } Field field = Test.class.getDeclaredField("var"); field.setAccessible(true); // 获取字段上的注解 B b2 = field.getAnnotation(B.class); if (b2 != null) { System.out.println("2-id:" + b2.id()); System.out.println("2-text:" + b2.text()); } // 获取方法上的注解 Method method = Test.class.getDeclaredMethod("empty"); B b3 = method.getAnnotation(B.class); if (b3 != null) { System.out.println("3-id:" + b3.id()); System.out.println("3-text:" + b3.text()); } } }

    输出结果:

    复制代码
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    er-hljs
    1-id:-1 1-text:I got you today 2-id:1 2-text:hello 3-id:-1 3-text:world

    好了,到这里,上面已经知道我们可以通过反射获取注解信息,但是目前仅仅只是获取信息而已,如何实现其他功能呢?这个问题问的好,后续再来填坑吧,不过还是一样,离不开反射。

    以上就是注解的基本内容了。

    最后的最后

    由本人水平所限,难免有错误以及不足之处, 屏幕前的靓仔靓女们 如有发现,恳请指出!

    最后,谢谢你看到这里,谢谢你认真对待我的努力,希望这篇博客对你有所帮助!

    你轻轻地点了个赞,那将在我的心里世界增添一颗明亮而耀眼的星!

  • 相关阅读:
    【Nginx】nginx | 微信小程序验证域名配置
    Linux下protobuf和 protobuf-c安装使用
    【Kafka】Kafka生产者数据重复、数据有序、数据乱序-07
    如何推广你的联盟计划:10种行之有效的营销方式
    java计算机毕业设计智能停车场管理系统源程序+mysql+系统+lw文档+远程调试
    找不到vcruntime140.dll,无法继续执行代码的详细解决方案
    助力AR眼镜轻量化,国内攻破二维扩瞳几何光波导量产
    大一学生期末大作业 html+css+javascript网页设计实例【电影购票项目】html网页制作成品代码
    【无标题】
    高可用之战:Redis Sentinal(哨兵模式)
  • 原文地址:https://www.cnblogs.com/god23bin/p/relearn-annotation.html