• java每日一记 —— 第一次研究注解


    本篇代码在jdk11中测试通过

    1.在写这篇文章之前我说点废话,就在昨晚我和一位技术大佬聊天时他说到,之前有位面试官问他”spring中有多少注解“。我尼玛,反正我是不知道的,后来他给我说了有多少来着我也忘记了。。。。。😣😣😣😣
    2.以前一直听说自定义注解需要反射,正好昨天我写了反射,那我就想着今天学习一下注解吧。。。。。。。我真的好无聊。。。废话连篇。。。语句不通顺。。。错别字连天。。。

    1.万事开头难:什么是注解

    1.定义:Java中的注解是一种元数据,可以被插入到源文件中,但并不会影响程序的实际逻辑。注解可以在编译时或运行时被读取和处理。它可以用于生成文档、执行编译时检查、跟踪代码依赖性以及执行其他形式的静态分析和代码理解任务

    这个定义我也看不懂,这到底是TM什么东西????????

    2.老规矩,我用生活中的案例说说,请看:

    在开始之前,我想先问大家一个问题:你们有没有过这样的经历,在看一部电影的时候,突然出现一段字幕解释剧情或者人物背景?这就是一种注解,它可以帮助我们更好地理解和欣赏电影。同样,在编程语言Java中,也有这样一种机制,那就是注解

    3.注解出现的位置:Java代码中的包、类型、构造方法、方法、成员变量、参数、本地变量的声明都可以用注解来修饰。注解本质上可以看作是一种特殊的标记,程序在编译或者运行时可以检测到这些标记而进行一些特殊的处理

    4.关于注解的处理:博主很🥬,🥬到这一块不清楚,有兴趣的伙伴可以自行研究

    5.小结一下:我们常常使用的注解,@Data、@Controller等等,这些都是注解,创建一个注解,也很简单,创建一个类,然后将class改为 @interface就是一个注解啦

    2.java的注解到底有什么用?

    下面这个案例是博主的理解和一些看法,就当个笑话看了。可能博主说的也不对,见笑了。。。。。

    1.首先,Java注解可以帮助我们生成文档。通过在代码中添加注解,我们可以自动生成Javadoc文档,这对于团队协作和代码维护非常有用

    2.其次,Java注解可以用于编译检查。例如,我们可以通过@Override注解来检查一个方法是否正确地重写了超类中的方法。这样,我们就可以在编译阶段发现并修复错误,而不是等到运行时才发现问题

    3.此外,Java注解还可以用于编译时和运行时的动态处理。例如,Spring框架就大量使用了注解来进行依赖注入和切面编程。这使得我们的代码更加简洁,也更易于维护

    4.最后,Java注解还可以用于追踪代码的依赖性,从而实现替代配置文件的功能。这样,我们就无需手动编辑繁琐的配置文件,而是可以直接在代码中进行设置

    5.总的来说,Java注解是一个强大的工具,它可以极大地提高我们的开发效率,并且有助于我们写出更高质量的代码。我希望今天的分享能帮助大家更好地理解和利用Java注解

    3.元注解

    元注解有五个:@Target、@Retention、@Documented、@Inherited、@Repeatable

    3.1.@Target

    1.源码:

    package java.lang.annotation;
    
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Target {
        ElementType[] value();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2.在 @Target 注解中有一个 ElementType 的约束,它告诉编译器,这个自定义的注解只能用于指定的类型

    3.进入ElementType源码

    package java.lang.annotation;
    
    public enum ElementType {
        // 类、接口(包括注释类型)或枚举声明
        TYPE,
    
        // 字段声明(包括枚举常量)
        FIELD,
    
        // 方法声明
        METHOD,
    
        // 形式参数声明
        PARAMETER,
    
        // 构造函数声明
        CONSTRUCTOR,
    
        // 局部变量声明
        LOCAL_VARIABLE,
    
        // 注释类型声明
        ANNOTATION_TYPE,
    
        // 包
        PACKAGE,
    
        // 类型参数声明
        TYPE_PARAMETER,
    
        // 类型的使用
        TYPE_USE,
    
        // 模块声明
        MODULE
    }
    
    • 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

    4.说明了注解所修饰的对象范围:注解可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)

    3.2.@Retention

    1.源码:

    package java.lang.annotation;
    
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Retention {
        
        RetentionPolicy value();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.这个注解是什么意思博主我也不清楚,博主真的很菜。。。。于是博主问了下gpt:

    它可以控制其修饰的注解的生命周期。它可以设置注解的生命周期为SOURCE、CLASS或RUNTIME,其中SOURCE表示注解只存在于源码中,不会被编译进字节码;CLASS表示注解会被编译进字节码,但在运行时不可见;RUNTIME则表示注解不仅会被编译进字节码,而且在运行时也可以访问到。通过设置不同的生命周期,我们可以控制注解在何时何地可用

    3.看了gpt的解释,恍然大悟,这和Lombok不是差不多的吗?在Lombok注解中我们常用的@Data注解不就是通过注解在编译时自动生成一部分代码,让源码看起来更简洁,字节码却很强大。。。好吧今天我学到了。。。。

    4.我们看看约束的源码:

    package java.lang.annotation;
    
    public enum RetentionPolicy {
        // 注释将被编译器丢弃
        SOURCE,
    
        // 注释将由编译器记录在类文件中,但运行时不需要被虚拟机保留。
        // 这是默认值行为。
        // 注释将被编译器丢弃
        CLASS,
    
        // 注释将由编译器记录在类文件中,并在运行时由VM保留,因此它们可能被反射地读取
        RUNTIME
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    5.这里不多解释了,这个注解在开发中真的很少用,用数学的角度看 limit x->0

    3.3.@Documented

    1.源码:

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Documented {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.源码什么都没:博主直接说他有什么用吧!!!!!它用来标记一个注解应该被包含在JavaDoc中。当我们在Java类或方法上使用了这个注解时,它的信息就会出现在对应的JavaDoc中,这对于其他人阅读和理解我们的代码非常有帮助

    3.4.@Inherited

    1.源码:

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Inherited {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.这怎么又没成员?看了几篇博客加上结合自己的理解总结一下:它允许子类继承父类上的注解。当我们在一个类上使用了@Inherited修饰的注解,并且该类有一个子类,则子类也会自动继承这个注解

    3.通俗的说法:

    • 首先,让我们从一个故事开始。想象一下,你在编写一段代码时,突然发现了一个错误,你需要修复它。然而,当你试图修改这段代码时,却发现同样的错误也出现在其他几个类中。这时,你会怎么做呢?可能你会逐一修改这些类,但这无疑是一项繁琐且耗时的工作。而这就是我们引入“@Inherited注解”的原因。

    • 那么,“@Inherited注解”究竟是什么呢?简单来说,它是一个允许子类继承父类中的元数据的注解。这意味着,如果你在一个父类上添加了“@Inherited注解”,那么所有继承自这个父类的子类都将自动获得这个注解。

    3.5.@Repeatable

    1.源码

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Repeatable {
       
        Class<? extends Annotation> value();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2.说白了它就是允许一个注解可以被使用一次或者多次

    3.举个案例:

    在我们开发一款音乐播放器时,可能需要处理各种类型的音乐文件。我们可以为每种文件类型创建一个单独的注解,如@MP3、@WAV等。但是,如果一首歌曲包含了多种格式的音频文件,我们就需要在一个类上多次应用这些注解。这就是@Repeatable注解发挥作用的地方

    4.小结一下:@Repeatable注解是一个强大的工具,可以帮助我们更好地管理和组织代码。我希望我的分享能帮助大家更好地理解和使用这个特性

    4.自定义注解使用

    博主从4个方面说起,类,构造,方法,成员属性

    4.1.定义注解

    1.定义在类上使用的注解

    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    // 定义可以被文档工具文档化
    @Documented
    // 声明周期为runtime,运行时可以通过反射拿到
    @Retention(RetentionPolicy.RUNTIME)
    // 注解修饰范围为类、接口、枚举
    @Target(ElementType.TYPE)
    public @interface AndyClassAnnotation {
        // 默认名称
        String name() default "defaultAndyClass";
        // 版本号
        String version() default "1.0.0";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    2.定义在构造器上使用的注解

    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    // 定义可以被文档工具文档化
    @Documented
    //注解修饰范围为构造
    @Target(ElementType.CONSTRUCTOR)
    // 声明周期为runtime,运行时可以通过反射拿到
    @Retention(RetentionPolicy.RUNTIME)
    public @interface AndyConstructorAnnotation {
        // 构造器名称
        String constructorName() default "";
        // 备注
        String remark() default "构造器";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    3.定义在方法上使用的注解

    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    // 定义可以被文档工具文档化
    @Documented
    // 声明周期为runtime,运行时可以通过反射拿到
    @Retention(RetentionPolicy.RUNTIME)
    // 注解修饰范围为方法
    @Target(ElementType.METHOD)
    public @interface AndyMethodAnnotation {
        
        // 名称
        String name() default "defaultAndyMethod";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    4.定义在成员属性上使用的注解

    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    
    // 定义可以被文档工具文档化
    @Documented
    // 修饰范围成员
    @Target(ElementType.FIELD)
    // 声明周期为runtime,运行时可以通过反射拿到
    @Retention(RetentionPolicy.RUNTIME)
    public @interface AndyFieldAnnotation {
    
        // key
        String name() default "defaultAndyName";
        // value
        String value() default "defaultAndyValue";
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    4.2.使用注解

    1.创建一个类

    package com.andy.zhu_jie.test;
    
    import com.andy.zhu_jie.ding_yi.AndyClassAnnotation;
    import com.andy.zhu_jie.ding_yi.AndyFieldAnnotation;
    import com.andy.zhu_jie.ding_yi.AndyMethodAnnotation;
    
    /**
     * @author Andy
     * @version 0.0.1
     * @since 2023-11-19 13:15
     */
    @AndyClassAnnotation(name = "TestClassAnnotation", version = "2.0.0")
    public class TestClassAnnotation {
    
        @AndyFieldAnnotation(name = "name", value = "这是一个测试")
        private String name;
    
        // @AndyConstructorAnnotation(constructorName ="这是构造器")
        // public TestClassAnnotation(String name) {
        //     this.name = name;
        // }
    
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @AndyMethodAnnotation(name = "sayHello")
        public void sayHello(){
            System.out.println("你好注解");
        }
    }
    
    
    • 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

    4.3.获取注解的类容

    1.获取类注解

    public class TestMain {
        private static TestClassAnnotation testClassAnnotation = new TestClassAnnotation();
    
        public static void main(String[] args) {
    
            Class<?> clazz = testClassAnnotation.getClass();
            // 因为注解是作用于类上面的,所以可以通过isAnnotationPresent来判断是否是一个具有指定注解的类
            if (clazz.isAnnotationPresent(AndyClassAnnotation.class)) {
                System.out.println("获取到了类注解");
                // 通过getAnnotation可以获取注解对象
                AndyClassAnnotation annotation = clazz.getAnnotation(AndyClassAnnotation.class);
                if (null != annotation) {
                    System.out.println("类名称 = " + annotation.name());
                    System.out.println("类版本 = " + annotation.version());
                } else {
                    System.out.println("没有获取到注解");
                }
            } else {
                System.out.println("这是一个类注解");
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    2.完整代码

    package com.andy.zhu_jie.test;
    
    import com.andy.zhu_jie.ding_yi.AndyClassAnnotation;
    import com.andy.zhu_jie.ding_yi.AndyConstructorAnnotation;
    import com.andy.zhu_jie.ding_yi.AndyFieldAnnotation;
    import com.andy.zhu_jie.ding_yi.AndyMethodAnnotation;
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    
    /**
     * @author Andy
     * @version 0.0.1
     * @since 2023-11-19 13:18
     */
    public class TestMain {
    
        public static void main(String[] args) throws ClassNotFoundException {
            Class<?> clazz = Class.forName("com.andy.zhu_jie.test.TestClassAnnotation");
            System.out.println("===类注解解析===");
            printClass(clazz);
    
            System.out.println("===成员变量注解解析===");
            printField(clazz);
    
            System.out.println("===成员方法注解解析===");
            printMethod(clazz);
    
            System.out.println("===构造器注解解析===");
            printConstructor(clazz);
    
        }
    
        /**
         * 打印类的注解
         */
        private static void printClass(Class<?> clazz) throws ClassNotFoundException {
            // 判断是否有AuthorAnnotation注解
            if (clazz.isAnnotationPresent(AndyClassAnnotation.class)) {
                // 获取AuthorAnnotation类型的注解
                AndyClassAnnotation annotation = clazz.getAnnotation(AndyClassAnnotation.class);
                System.out.println(annotation.name() + "\t" + annotation.version());
            }
        }
    
        /**
         * 打印成员变量的注解
         */
        private static void printField(Class<?> clazz) throws ClassNotFoundException {
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                if (field.isAnnotationPresent(AndyFieldAnnotation.class)) {
                    AndyFieldAnnotation annotation = field.getAnnotation(AndyFieldAnnotation.class);
                    System.out.println(annotation.name() + "\t" + annotation.value());
                }
            }
        }
    
        /**
         * 打印成员变量的注解
         */
        private static void printMethod(Class<?> clazz) throws ClassNotFoundException {
            Method[] methods = clazz.getDeclaredMethods();
            for (Method method : methods) {
                if (method.isAnnotationPresent(AndyMethodAnnotation.class)) {
                    AndyMethodAnnotation annotation = method.getAnnotation(AndyMethodAnnotation.class);
                    System.out.println(annotation.name() + "\t");
                }
            }
        }
    
        /**
         * 打印成员变量的注解
         */
        private static void printConstructor(Class<?> clazz) throws ClassNotFoundException {
            Constructor<?>[] constructors = clazz.getDeclaredConstructors();
            for (Constructor<?> constructor : constructors) {
                if (constructor.isAnnotationPresent(AndyConstructorAnnotation.class)) {
                    AndyConstructorAnnotation annotation = constructor.getAnnotation(AndyConstructorAnnotation.class);
                    System.out.println(annotation.constructorName() + "\t" + annotation.remark());
                }
            }
            System.out.println("无");
        }
    }
    
    • 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
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
  • 相关阅读:
    智慧校园-医务管理系统总体概述
    C++基础篇考题
    【C++技能树】手撕AVL树 --插入与旋转详解
    Java 进阶面试问题列表
    [记忆化dfs]leetcode2400:恰好移动 k 步到达某一位置的方法数目(medium)
    使用UiPath和AA构建的解决方案 5. 使用UiPath ReFramework处理采购订单
    巧用 redis 实现点赞功能,它不比 mysql 香吗?
    竹云董事长董宁受邀出席香港第三届湾区元宇宙大会暨AIGC、RWA发展高峰论坛并作主题演讲
    多数据源切换
    【无标题】
  • 原文地址:https://blog.csdn.net/weixin_44702984/article/details/134488526