• 注解【元数据,自定义注解等概念详解】(超简单的好吧)



    在这里插入图片描述

    注解的释义

    我们都知道注释是拿来给程序员看的,而注解就是给程序(或者说JVM)看的。

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

    在这里插入图片描述

    元数据的含义

    在Java中,元数据的含义是指描述Java类、接口、字段和方法等元素的数据。这些数据提供了关于Java程序元素的额外信息,可以用于反射、注解处理、代码生成等场景。通过元数据,我们可以在运行时获取和操作程序元素的属性、注解等信息,从而实现更灵活和动态的编程能力。

    基础阶段常见的注解

    我们在基础阶段常见的注解并没有多少,很少用到复杂注解,

    注解Value
    @Override(重写或者覆盖)一般放在方法前面,表示方法在子类中重写了超类中的方法
    @Deprecated可放在类、方法或字段前面,表示一个类、方法或字段已经过时了,不应该再使用
    @SuppressWarnings一般放在程序代码靠前位置,抑制编译器产生的警告,这个注解后面有大括号是可以传参的,一般图方便就是写个“all”
    @Native一般放在方法前面,指示一个方法是native方法,即由本地代码实现的方法
    @Test一般用于测试,可以不用启动整个项目就能调用部分代码

    注解的作用(包括但不限于)

    标记检查

    注解可以用来标记一些需要被检查的代码,以便进行静态检查或动态检查。

    代码生成

    注解可以被用来生成代码,如JUnit注解可以根据测试方法定义生成测试用例。

    生成文档

    特定的注解可以用来生成对应文档,例子是在注释里的注解

    拿最后一个举个例子
    编写一个演示类

    package budiu.budiu;
    
    /**
     * 这是一个名字为DocDemo的类
     *
     * @Author:罗不丢
     * @Version:1.0
     */
    
    public class DocDemo {
        /**
         * @param  name
         * @return java.lang.String
         **/
        public String play (String name){
            return name+"正在玩";
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    注意编码格式别搞错,有中文的把UTF-8换成GBK
    在这里插入图片描述

    用javadoc命令生成文档
    在这里插入图片描述

    就会生成一堆的前端文件,可以当做文档查看
    在这里插入图片描述

    教你读懂注解内部代码内容

    在想要了解注解的内部代码,我们要先了解五种元注解(元注解的意思是注解的最基本组成部分,任何注解都要由它们组成)

    五种元注解

    @Retention:
    它只有一个参数:RetentionPolicy,用于指定注解的生命周期。
    这个参数有三个可能的值:
    RetentionPolicy.SOURCE(注解仅在源代码中存在,编译时会被丢弃),
    RetentionPolicy.CLASS(注解在编译时会被保留,在JVM中运行时不可见)
    RetentionPolicy.RUNTIME(注解在运行时还会存在,可以通过反射机制读取它的信息)。

    @Target:
    它有一个参数:ElementType,用于指定注解可以应用到的Java元素类型。
    例如,方法可以被注解,字段可以被注解,甚至注解本身也可以被注解。
    ElementType包含多种可能的值,如ElementType.METHODElementType.FIELDElementType.ANNOTATION_TYPE等。

    @Documented:
    这个元注解没有参数。它的作用是表示使用该注解的注解信息能够被javadoc或类似工具文档化。

    @Inherited:
    这个元注解也没有参数。表示被它修饰的注解具有继承性,即如果一个类声明了被@Inherited修饰的注解,那么它的子类也将具有这个注解。
    注意:不是注解继承父注解,而是子类继承父类里的注解。

    @Repeatable:
    这个元注解是Java 1.8引入的,它表示一个注解可以被重复使用在一个声明上。它有一个参数,这个参数是用来定义该注解的“容器”注解类型,用于存储重复注解的数据。(还没怎么了解过)

    尝试解读简单注解

    这里以最简单的@Override注解(JDK1.8版本)为引入

    /*
     * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
     * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
     */
    
    package java.lang;
    
    import java.lang.annotation.*;
    
    /**
     * Indicates that a method declaration is intended to override a
     * method declaration in a supertype. If a method is annotated with
     * this annotation type compilers are required to generate an error
     * message unless at least one of the following conditions hold:
     * 指示方法声明旨在重写
     * 超类型中的方法声明。如果方法被注释
     *需要此注释类型编译器才能生成错误
     *消息,除非至少满足以下条件之一:
     *
     *
     * 
    • * The method does override or implement a method declared in a * supertype. * 该方法会重写或实现在 * 超类型。 *
    • * The method has a signature that is override-equivalent to that of * any public method declared in {@linkplain Object}. * 该方法具有覆盖等效于 * 在 {@linkplain Object} 中声明的任何公共方法。 *
    * * @author Peter von der Ahé * @author Joshua Bloch * @jls 9.6.1.4 @Override * @since 1.5 */
    @Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
    • 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
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    除去注释后,代码并不算长,我们只需要了解三行,

    @Target(ElementType.METHOD):
    这是一个元注解,它用来指明Override注解可以用在什么地方。在这个例子中,它表示Override只能用在方法上。ElementType.METHOD表示这个注解的目标是方法。

    @Retention(RetentionPolicy.SOURCE):
    这是另一个元注解,用来指明注解的保留策略。这里指定的是RetentionPolicy.SOURCE,意味着这个注解只保留在源代码中,编译成.class文件时它将被丢弃。也就是说,这个注解在运行时是不可见的。

    public @interface Override:
    这定义了一个名为Override的注解接口。@interface是用来声明一个注解的关键字。这个接口是公开的(public),意味着它可以被任何人访问。

    再试着解读一个

    
    /**
     * Indicates that the named compiler warnings should be suppressed in the
     * annotated element (and in all program elements contained in the annotated
     * element).  Note that the set of warnings suppressed in a given element is
     * a superset of the warnings suppressed in all containing elements.  For
     * example, if you annotate a class to suppress one warning and annotate a
     * method to suppress another, both warnings will be suppressed in the method.
    
    
     * 表示应在
     * 带注释的元素(以及包含在带注释的
     * 元素)。 请注意,给定元素中禁止显示的警告集是
     * 在所有包含元素中禁止显示的警告的超集。 为
     * 例如,如果您对一个类进行注释以禁止显示一个警告并注释一个警告
     * 方法来抑制另一个,两个警告都会在该方法中被禁止。
     *
     *
     * 

    As a matter of style, programmers should always use this annotation * on the most deeply nested element where it is effective. If you want to * suppress a warning in a particular method, you should annotate that * method rather than its class. * *作为风格问题,程序员应该始终使用此注解 * 在最深嵌套的元素上,它有效。 如果你想 * 在特定方法中禁止警告,您应该注释 * 方法而不是它的类。 * @author Josh Bloch * @since 1.5 * @jls 4.8 Raw Types * @jls 4.12.2 Variables of Reference Type * @jls 5.1.9 Unchecked Conversion * @jls 5.5.2 Checked Casts and Unchecked Casts * @jls 9.6.3.5 @SuppressWarnings */ @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) @Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings { /** * The set of warnings that are to be suppressed by the compiler in the * annotated element. Duplicate names are permitted. The second and * successive occurrences of a name are ignored. The presence of * unrecognized warning names is not an error: Compilers must * ignore any warning names they do not recognize. They are, however, * free to emit a warning if an annotation contains an unrecognized * warning name. * *

    The string {@code "unchecked"} is used to suppress * unchecked warnings. Compiler vendors should document the * additional warning names they support in conjunction with this * annotation type. They are encouraged to cooperate to ensure * that the same names work across multiple compilers. * @return the set of warnings to be suppressed */ String[] value(); }

    • 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
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    这个注解是Java的@SuppressWarnings注解,它的定义解释了它的功能和用法。现在,我们来详细解读一下这个注解:

    @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}):
    这是@SuppressWarnings注解的元注解,指明了它可以用于什么类型的Java元素。具体来说,它可以用于类、字段、方法、参数、构造器和局部变量。它的作用范围非常广泛。

    @Retention(RetentionPolicy.SOURCE):
    这个元注解表示@SuppressWarnings注解的保留策略是SOURCE,也就是说它仅在源代码中存在,编译时会被丢弃。因此,这个注解不会影响运行时的行为。

    public @interface SuppressWarnings:
    这定义了名为SuppressWarnings的注解。当开发者想要抑制编译器产生的某些警告时,可以使用这个注解。

    String[] value();:
    这是@SuppressWarnings注解的唯一 一个元素。它是一个字符串数组,表示要抑制的警告的名称。例如,如果你想要抑制未经检查的转换警告,你可以使用"unchecked"字符串。编译器会忽略这些特定的警告。需要注意的是,这里提到编译器应该忽略它们不认识的警告名称,但是也可以自由选择在遇到不识别的警告名称时发出警告。

    我当时的疑惑点

    在解读SuppressWarnings注解时,为什么最后一段的大括号里就定义了一个字符串数组,一点逻辑都看不出来,要按照之前的思维来说,它怎么识别到字符串并判断抑制特定警告的,不应该有一些内部方法来完成吗?
    在这里插入图片描述

    其实在Java编译器中,当解析和处理Java代码时,会检查代码上的注解。当编译器遇到@SuppressWarnings注解时,它会查看注解中的value数组,这些值表示需要抑制的警告类型。

    编译器内部会有一个警告名称与警告类型的映射逻辑。当编译器遇到特定的代码模式或情况时,它会生成相应的警告。当存在@SuppressWarnings注解时,编译器会检查生成的警告名称是否匹配注解中指定的名称,如果匹配,则不会将该警告显示给开发者。

    需要注意的是,这个匹配和抑制逻辑是由编译器实现的,而不是由注解本身实现的。不同的编译器可能会对@SuppressWarnings注解中的警告名称有不同的处理方式,但通常都会遵循Java语言规范中定义的行为。

    虽然@SuppressWarnings注解本身只是一个标记,但通过编译器的逻辑处理,它能够实现警告的抑制功能。
    在这里插入图片描述

    所以我们不太需要关注注解的逻辑,就单纯把它理解成一种给编译器识别的标识符号就好。

    自定义注解

    除了官方提供的注解,我们也可以自定义注解,并使用自定义注解。
    自定义注解的一些规范:
    注解定义:

    注解使用@interface关键字进行定义。

    元注解:

    为你的自定义注解提供元注解,特别是@Target和@Retention,以明确你的注解的用途和生命周期。

    成员变量:

    注解中的成员变量代表注解的参数。这些成员变量只能是基本类型、String、Class、枚举类型、注解类型,或者这些类型的数组。

    默认值:

    为注解的成员变量提供一个默认值。

    命名规范:

    注解的名称应该是描述性的,并且应该使用名词。同时,为了避免混淆,建议为注解名称加上一个特定的前缀。

    自定义注解举例

    自定义注解的例子——@RepeatableHint。这个注解的目的是给开发者提供一个可重复的提示信息。

    首先,定义 Hint 注解:
    在这里插入图片描述

    import java.lang.annotation.*;  
      
    @Retention(RetentionPolicy.RUNTIME)  //会保留到运行时阶段
    @Target(ElementType.TYPE)  //限制使用范围是类,枚举,接口
    public @interface Hint {  
        String value();  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    接着,定义 RepeatableHint 注解:

    import java.lang.annotation.*;  
      
    @Retention(RetentionPolicy.RUNTIME)  //会保留到运行时阶段
    @Target(ElementType.TYPE)  //限制使用范围是类,枚举,接口
    public @interface RepeatableHint {  
        Hint[] hints() default {};  //算是个嵌套吧,但底层仍是字符串数组
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    注意这里 RepeatableHint 注解中定义了一个名为 hints 的成员,它的类型是 Hint[],默认值为空数组。这样就可以在使用 RepeatableHint 注解时,重复使用 Hint 注解。

    然后在需要的类或者接口或枚举类上使用这些注解:

    @RepeatableHint(hints={
            @Hint("这是一个测试类的提示1"),
            @Hint("提示2")
    })
    public class TestClass {  
          
        public static void main(String[] args) {  
            // 获取TestClass的Class对象  
            Class<TestClass> testClass = TestClass.class;  
              
            // 判断TestClass是否带有RepeatableHint注解  
            if (testClass.isAnnotationPresent(RepeatableHint.class)) {  
                // 获取RepeatableHint注解对象  
                RepeatableHint repeatableHint = testClass.getAnnotation(RepeatableHint.class);  
                  
                // 获取所有的Hint注解对象  
                Hint[] hints = repeatableHint.hints();  
                  
                // 遍历并打印所有的提示信息  
                for (Hint hint : hints) {  
                    System.out.println(hint.value());  
                }  
            }  
        }  
    }
    
    
    • 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

    在处理这些注解时,可以通过反射获取到所有的 Hint 注解,并对其进行处理。例如,可以在程序启动时,通过解析这些注解,将提示信息打印到日志中或者进行其他处理。
    在这里插入图片描述

    注解的原理

    注解的原理基于Java的反射机制。

    注解本质:
    注解本质上是一个接口,它继承了java.lang.annotation.Annotation接口。当我们定义一个注解时,实际上定义了一个新的接口。

    动态代理:
    当我们通过反射获取注解时,返回的对象实际上是Java运行时生成的动态代理对象。这意味着我们调用注解的方法时,实际上是在调用动态代理对象上的方法。

    AnnotationInvocationHandler:
    这是Java内部用于处理注解调用的一个类。当我们通过动态代理对象调用注解的方法时,最终会调用AnnotationInvocationHandlerinvoke方法。这个方法会从一个叫做memberValues的Map中索引出对应的值。

    memberValues的来源:
    memberValues中的数据来源于Java的常量池。这就是为什么我们可以在注解中定义一些常量,然后这些常量的值会被存储在Java的常量池中。

    元注解的角色:
    元注解如@Retention, @Target等,它们的作用是描述注解的特性。例如,@Retention定义了注解的生命周期,@Target定义了注解可以应用的目标。

    运行时处理:
    大部分注解的处理都是在运行时完成的。例如,框架可能会在运行时扫描某个特定的注解,并根据该注解的属性修改框架的行为。

    总的来说,注解提供了一种元编程的方式。我们可以在不改变原有代码逻辑的情况下,通过添加注解来改变代码在编译后或者运行时的行为。而这一切都是基于Java的反射机制以及动态代理技术实现的。

    总结

    虽然注解在Java SE阶段用的很少,但在框架学习中,比如SpringBoot中将会成为常客,我们也会从面向对象编程逐渐转化为面向注解编程🤭🤭🤭🤭🤭。
    在这里插入图片描述

  • 相关阅读:
    【C++】vector的模拟实现不会怎么办?看过来
    SSM+基于SSM的评教系统 毕业设计-附源码281157
    lesson2(补充)关于const成员函数
    【Java分享客栈】Java程序员为争一口气熬夜硬刚CSS实现掘金首页
    长牌游戏功能整理
    Struts2的拦截器
    Python绘图系统18:导入txt格式数据
    CSP-J/S信息学奥赛-算法
    Python入门基础篇
    聊一聊Redis的离线分析
  • 原文地址:https://blog.csdn.net/weixin_73956489/article/details/134444701