• 彻底玩转Java注解和反射


    前言

    java中尤其是大量框架中经常可以在属性、方法、类上看到注解,而反射与注解是最完美的一对,通过反射可以对注解进行操作。只有对注解及反射有深入的了解,才能理解很多诸如Spring,Springboot等框架。本文详解讲解java中的注解及java反射,通过本文能够使您对注解及反射有更清晰的认识。

    一、注解

    1. 什么是注解

    注解是从JDK5.0引入的技术,它不仅可以对程序中的package、class、method、field等进行解释也可以通过反射被其它程序(如编译器)读取,它的格式是:@注释名(参数)。

    2. 内置注解介绍

    java本身提供了一些内置注解,常用的内置注解包括:

    • @Override 只能作用在方法上,表示覆盖超类中的方法声明
    • @Deprecated 表示不鼓励使用的程序元素,通常是因为它是危险的,或者因为存在更好的替代方法。
    • @SuppressWarnings(“all”) 表示在注释元素(以及注释元素中包含的所有程序元素)中应该抑制命名的编译器警告一般不建议使用,因为加了这个注释,一些明显的错误,我们会不容易发现

    对于内置注解使用的代码示例:

    //了解内置注解
    public class Test01 extends Object {
        //@Override 表示方法声明旨在覆盖超类型中的方法声明
        @Override
        public String toString() {
            return super.toString();
        }
        //注释@Deprecated的程序元素是程序员不鼓励使用的程序元素,通常是因为它是危险的,或者因为存在更好的替代方法。
        @Deprecated
        public static void test()
        {
            System.out.println("废弃注解");
        }
        @SuppressWarnings("all")//一般不建议使用,因为加了这个注释,一些明显的错误,我们会不容易发现
        public static void test01()
        {
            int i=0;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    3. 元注解介绍

    元注解是对注解的注解,即对注解进行解释说的注解。元注解包括:

    • @Target 表示注解可以应用在哪些地方(TYPE, FIELD,METHOD,PARAMETER,CONSTRUCTOR,等等)
    • @Retention 表示注解在什么地方有效(SOURCE,CLASS,RUNTIME)
    • @Documented 表示是否将注解记录在javadoc和类似工具中。
    • @Inherited 表示注释类型自动继承,即子类可以继承父类的注解

    如上面介绍的内置注解@Override定义如下:

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Override {
    }
    
    • 1
    • 2
    • 3
    • 4

    其中元注解@Target指明了Override 注解只能作用于方法上,元注解@Retention指明Override 注解只在源代码级别有效。

    4. 自定义注解

    使用@interface自定义注解,其自动继承java.lang.annotation.Annotation接口,自定义注解格式如下:
    @interface 注解名{
    参数类型 参数名();
    }

    • 自定义没有参数的注解myAnnotation示例代码如下:
    //自定义注解
    //Target 表示注解可以应用在哪些地方
    @Target(value= {ElementType.METHOD, ElementType.TYPE})
    //Retention 表示注解在什么地方有效
    @Retention(value = RetentionPolicy.RUNTIME)
    //Documented表示是否将注解记录在javadoc和类似工具中。
    @Documented
    //Inherited 表示注释类型自动继承,即子类可以继承父类的注解
    @Inherited
    @interface myAnnotation{
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 自定义带参数的注解myAnnotation01示例代码如下,其中default用于设置默认值:
    @Target({ElementType.TYPE,ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @interface myAnnotation01{
        //注解参数定义格式:类型 参数名称();
        //default用于设置默认值,没有默认值,则必须在使用的时候赋值
        String name() default "";
        int age() default 0;
        int id() default -1;
        String[] schools() default {"清华","北大"};
    }
    
    
    //使用该注解示例
     @myAnnotation01(name = "Mekeater")
        private void test()
        {
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 自定义带一个参数的注解,建议参数名字为value,这样在使用的时候可以不用写参数名,否则必须有参数名,示例代码如下
    @Target({ElementType.TYPE,ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @interface myAnnotation02{
        String value();
    }
    
    
    //使用该注解示例
    //因为只有一个参数,切参数名为value,因此可以省略参数名
        @myAnnotation02("Mekeater")
        private void test1()
        {
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    二、反射

    1. 什么是反射

    了解反射前,我们要先知道什么是动态语言,什么是静态语言。

    • 动态语言是指在运行时可以改变其结构的语言,如程序运行时新的函数,对象,甚至代码可以被引进。主要的动态语言包括C#,JavaScript,Python等等,以JavaScript为例来说明它如何在程序运行时改变其结构。
    function f() {
        var x="var a=6; var b=6; alter(a+b)";
        eval(x);//可以程序运行时,执行代码,将x的字符串改变为数值
    }
    
    • 1
    • 2
    • 3
    • 4
    • 静态语言是指在运行时不可以改变其结构的语言。主要的静态语言包括Java,C,C++等。
    • Java的反射机制允许程序在执行期间通过Reflection API获取类的内部信息,并能直接操作任意对象的内部属性及方法,使得Java成为一门“动态语言”。

    2. 如何获得反射对象

    JVM加载完类后,就自动在堆内存的方法区中产生了一个Class类型的对象(一个类只有一个Class对象),该Class对象包含了完整的类结构信息,因此这个Class对象就像是类的一面镜子,通过这个镜子可以看到类本身的模样(结构),所以这个Class对象也称为反射对象。
    Java提供了三种方式获取Class对象(反射对象):

    • 1.已知类名及其完整包名,通过forName获取类的Class对象
    Class c1= Class.forName("com.sun.reflection.User");
    
    • 1
    • 2.已有实例化的类对象,通过对象的getClass方法获取Class对象
    User user = new User();
    Class c4 = user.getClass();
    
    • 1
    • 2
    • 3.已知类名,通过类名.class属性获取Class对象
    Class c5 = User.class;
    
    • 1

    通过以上三种方式我们可以获得任意类的反射对象,常用类型的反射对象获取的示例代码如下:

    public static void main(String[] args) {
            Class c1 = Object.class;//类
            Class c2 = Comparable.class;//接口
            Class c3=String[].class;//一维数组
            Class c4=int[][].class;//二维数组
            Class c5=Override.class;//注解
            Class c6= ElementType.class;//枚举
            Class c7=Integer.class;//基本数据类型
            Class c8=void.class;
            Class c9=Class.class;
    
            //测试发现,只要元素类型、维度一样,就是同一个Class对象
            int[] a = new int[9];
            int[] b = new int[99];
            System.out.println(a.getClass().hashCode());
            System.out.println(b.getClass().hashCode());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    3. 通过反射获取该类是由什么类加载器加载的

    我们知道JVM包括四种类加载器,分别是引导类加载器,扩展类加载器、系统类加载器、自定义类加载器,这四种加载依据双亲委派机制进行工作。
    在这里插入图片描述

    • 引导类加载器是由C++编写,负责Java平台核心库,用来装载核心类库。该加载器无法直接获取。
    • 扩展类加载器负责加载jre/lib/ext目录下的jar包。
    • 系统类加载器是最常用的加载器,主要负责java -classpath或-D java.class.path所指目录下的类与jar包装载工作。

    类的反射对象有getClassLoader方法,通过这个方法可以获得该类是由哪个加载器加载的,示例代码如下:

     public static void main(String[] args) throws ClassNotFoundException {
            //1. 获得系统类加载器
            ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
            System.out.println("统类加载器:"+systemClassLoader);
            //2. 获得扩展类加载器
            ClassLoader extClassLoader = systemClassLoader.getParent();
            System.out.println("扩展类加载器:"+extClassLoader);
            //3. 获得根加载器(C/C++编写的)
            ClassLoader bootClassLoader = extClassLoader.getParent();
            System.out.println("引导类加载器:"+bootClassLoader);
    
            //4. 测试指定类由哪个加载器加载的
            Class aClass = Class.forName("com.sun.reflection.test06");
            ClassLoader classLoader = aClass.getClassLoader();
            System.out.println("test06类由加载器:"+classLoader+" 加载");
    
            aClass=Class.forName("java.lang.Object");
            classLoader=aClass.getClassLoader();
            System.out.println("Object类由加载器:"+classLoader+" 加载");
    
            //5. 获取系统类加载器可以加载的路径
            //System.out.println(System.getProperty("java.class.path"));
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    运行结果:
    在这里插入图片描述

    4. 通过反射获取类的运行时结构

    1. 首先获取类的反射对象,如已知包名及类名获取User类的反射对象示例代码如下:
    Class aClass = Class.forName("com.sun.reflection.User");
    
    • 1
    1. 获得类的名称
    //getName方法获取类的包名+类名
    System.out.println(aClass.getName());
    //getSimpleName方法获取类名
    System.out.println(aClass.getSimpleName());
    
    • 1
    • 2
    • 3
    • 4
    1. 获得类的属性
            //getFields方法获取类的public属性
            Field[] fields = aClass.getFields();
            for (Field field:fields) {
                System.out.println(field);
            }
            //getDeclaredFields方法获取类的所有属性
            fields=aClass.getDeclaredFields();
            for (Field field:fields) {
                System.out.println("#"+field);
            }
            //getDeclaredField方法获取指定名称的属性字段
            Field age = aClass.getDeclaredField("age");
            System.out.println(age);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    1. 获得类的方法
            //getMethods获取本类及其父类的所有public方法
            Method[] methods = aClass.getMethods();
            for (Method method : methods) {
                System.out.println("getMethods "+method);
            }
            //getDeclaredMethods获取本类所有方法(公共及私有)
            methods=aClass.getDeclaredMethods();
            for (Method method : methods) {
                System.out.println("getDeclaredMethods "+method);
            }
            //getMethod获取本类指定名称的方法
            Method getName = aClass.getMethod("getName");
            Method setName = aClass.getMethod("setName", String.class);
            System.out.println(getName);
            System.out.println(setName);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    1. 获得类的构造方法
            //getConstructors获取public构造方法
            Constructor[] constructors = aClass.getConstructors();
            for (Constructor constructor : constructors) {
                System.out.println(constructor);
            }
            //getDeclaredConstructors获取全部构造方法
            constructors = aClass.getDeclaredConstructors();
            for (Constructor constructor : constructors) {
                System.out.println("#"+constructor);
            }
            //getConstructor获取指定的构造器
            Constructor constructor = aClass.getConstructor(String.class, int.class, int.class);
            System.out.println("*"+constructor);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    5. 通过反射动态创建对象及执行方法

    通过反射创建对象的两种方式,示例代码如下:

            //1. 直接通过反射调用无参构造器创建对象
            Class c1 = Class.forName("com.sun.reflection.User");
            User user =(User) c1.newInstance();//本质上是调用无参构造器,因此必须有无参构造
            System.out.println(user);
    
            //2. 通过反射对象先获取构造器,然后再用指定构造器创建对象
            Constructor constructor = c1.getConstructor(String.class, int.class, int.class);
            User mekeater = (User) constructor.newInstance("mekeater", 18, 1);
            System.out.println(mekeater);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    通过反射操作方法的示例代码如下:

            //1. 创建对象
            User user2 = (User) c1.newInstance();
            //2. 获取方法
            Method setName = c1.getMethod("setName", String.class);
            //3. 调用invoke执行方法,invoke方法第一个参数是执行方法所属的对象,第二个参数是执行方法所需的参数
            setName.invoke(user2,"mekeater");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    通过反射操作属性的示例代码如下:

            //1. 创建对象
            User user3 = (User) c1.newInstance();
            //2. 获取属性
            Field name = c1.getDeclaredField("name");
            //3. 因为反射不能直接操作私有属性,我们需要将属性或方法的可访问行设置为true
            name.setAccessible(true);
            //4. 通过set设置属性的值
            name.set(user3,"sun");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    6. 普通方法调用与反射调用方法性能对比

    下面代码测试普通方法调用与通过反射调用方法,及关闭验证后通过反射调用方法的性能对比:

    //普通方式,调用方法(效率最高)
        public static void test01()
        {
            User user = new User();
            long startTime = System.currentTimeMillis();
            for (int i = 0; i < 1000000000; i++) {
                user.getName();
            }
            long endTime = System.currentTimeMillis();
            System.out.println("普通方式调用对象方法,然后获取10亿次数据用时: "+ (endTime-startTime)+"ms");
        }
    
        //反射方式,调用方法(效率降低)
        public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
            User user = new User();
            Class c1 = user.getClass();
            Method getName = c1.getMethod("getName", null);
            long startTime = System.currentTimeMillis();
            for (int i = 0; i < 1000000000; i++) {
                getName.invoke(user,null);
            }
            long endTime = System.currentTimeMillis();
            System.out.println("反射方式调用对象方法,然后获取10亿次数据用时: "+ (endTime-startTime)+"ms");
        }
    
        //反射方式,调用方法,关闭访问验证(可以提高效率)
        public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
            User user = new User();
            Class c1 = user.getClass();
            Method getName = c1.getMethod("getName", null);
            getName.setAccessible(true);
            long startTime = System.currentTimeMillis();
            for (int i = 0; i < 1000000000; i++) {
                getName.invoke(user,null);
            }
            long endTime = System.currentTimeMillis();
            System.out.println("反射方式且关闭检测后,调用对象方法,然后获取10亿次数据用时: "+ (endTime-startTime)+"ms");
        }
    
        public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
            test01();
            test02();
            test03();
        }
    
    • 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

    运行结果:
    在这里插入图片描述从运行结果可以看到,通过反射调用方法的效率远远低于普通方法调用,但通过setAccessible关闭检测后,效率有所提高,因此如果我们必须用反射调用方法,建议关闭验证,提高执行效率。

    7. 通过反射获取方法的泛型信息

    具体步骤如下:

    1. 通过反射获取Method
    2. 通过Method的getGenericParameterTypes方法获取参数
    3. 判断参数是否属于ParameterizedType,如果属于,则调用getActualTypeArguments方法获取泛型的真实参数类型
        public static void main(String[] args) throws NoSuchMethodException {
            //1. 获取Method
            Method method = test10.class.getMethod("test01", Map.class, List.class,String.class);
            //2. 获得参数类型
            Type[] genericParameterTypes = method.getGenericParameterTypes();
            for (Type genericParameterType : genericParameterTypes) {
                System.out.println("#"+genericParameterType);
                //3. 判断是否属于ParameterizedType
                if (genericParameterType instanceof ParameterizedType)
                {
                    //4. 获取泛型中的真实参数类型
                    Type[] arguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
                    for (Type argument : arguments) {
                        System.out.println("*"+argument);
                    }
                }
            }
            
            method = test10.class.getMethod("test02",null);
            //同理,获取返回泛型类型
            Type genericReturnType = method.getGenericReturnType();
            if (genericReturnType instanceof ParameterizedType)
            {
                Type[] arguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();//获取泛型中的真实参数类型
                for (Type argument : arguments) {
                    System.out.println("$"+argument);
                }
            }
        }
    
    • 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

    8. 通过反射获取注解信息(常用)

    反射操作注解的常用方法如下示例代码所示:

    //反射操作注解
    public class test11 {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
            //1. 通过反射获取注解
            Class c1 = Class.forName("com.sun.reflection.Student");
            Annotation[] annotations = c1.getAnnotations();
            for (Annotation annotation : annotations) {
                System.out.println(annotation);
            }
            //2. 获取注解的value值
            table annotation = (table)c1.getAnnotation(table.class);
            System.out.println(annotation.value());
            //3. 获得类指定的注解
            Field name = c1.getDeclaredField("name");
            filed annotation1 = name.getAnnotation(filed.class);
            System.out.println(annotation1.columnName());
            System.out.println(annotation1.type());
            System.out.println(annotation1.length());
        }
    }
    @table("db_student")
    class Student{
        @filed(columnName = "db_id",type = "int",length = 9)
        private int id;
        @filed(columnName = "db_age",type = "int",length = 9)
        private int age;
        @filed(columnName = "db_name",type = "varchar",length = 6)
        private String name;
    
        public Student() {
        }
    
        public Student(int id, int age, String name) {
            this.id = id;
            this.age = age;
            this.name = name;
        }
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "id=" + id +
                    ", age=" + age +
                    ", name='" + name + '\'' +
                    '}';
        }
    }
    
    //类注解
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface table{
        String value();
    }
    
    //属性的注解
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @interface filed{
        String columnName();
        String type();
        int length();
    }
    
    • 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
    • 87

    三、参考链接

    狂神说Java

  • 相关阅读:
    zblog主题模板:zblog产品企业主题aymeighteen
    java项目容器化(docker)部署注意点
    苹果注定要输给欧盟,USB-C成为标准接口已是大势所趋
    文章提交秒收录软件
    开源的 Sora 复现方案,成本降低近一半!
    11.26
    vite+vue3+ts中的vue-router基本配置
    【TEC100TAI-KIT】青翼科技基于复微青龙JFMQL100TAI的全国产化智能异构计算平台
    linux 设置开机启动
    性能指标>软硬件的性能指标
  • 原文地址:https://blog.csdn.net/qq_34720818/article/details/126049814