0

0

java反射机制及常用应用场景是什么

王林

王林

发布时间:2023-04-26 21:01:05

|

1713人浏览过

|

来源于亿速云

转载

    一、什么是java反射?

    在java的面向对象编程过程中,通常我们需要先知道一个class类,然后new 类名()方式来获取该类的对象。也就是说我们需要在写代码的时候(编译期或者类加载之前)就知道我们要实例化哪一个类,运行哪一个方法,这种通常被称为静态的类加载。

    但是在有些场景下,我们事先是不知道我们的代码的具体行为的。比如,我们定义一个服务任务工作流,每一个服务任务都是对应的一个类的一个方法。

    java反射机制及常用应用场景是什么

    • 服务任务B执行哪一个类的哪一个方法,是由服务任务A的执行结果决定的

    • 服务任务C执行哪一个类的哪一个方法,是由服务任务A和B的执行结果决定的

      立即学习Java免费学习笔记(深入)”;

    • 并且用户不希望服务任务的功能在代码中写死,希望通过配置的方式根据不同的条件执行不同的程序,条件本身也是变化的

    面对这个情况,我们就不能用代码new 类名()来实现了,因为你不知道用户具体要怎么做配置,这一秒他希望服务任务A执行Xxxx类的x方法,下一秒他可能希望执行Yyyy类的y方法。当然你也可以说提需求嘛,用户改一次需求,我改一次代码。这种方式也能需求,但对于用户和程序员个人而言都是痛苦,那么有没有一种方法在运行期动态的改变程序的调用行为的方法呢?这就是要为大家介绍的“java反射机制”。

    那么java的反射机制能够做那些事呢?大概是这样几种:

    • 在程序运行期动态的根据package名.类名实例化类对象

    • 在程序运行期动态获取类对象的信息,包括对象的成本变量和方法

    • 在程序运行期动态使用对象的成员变量属性

    • 在程序运行期动态调用对象的方法(私有方法也可以调用)

    二、Hello World

    先入个门,大佬可以略过这一段。我们定义一个类叫做Student

    package com.zimug.java.reflection;
    public class Student {
        public String nickName;
        private Integer age;   //这里是private
        public void dinner(){
            System.out.println("吃晚餐!");
        }
        private void sleep(int minutes){   //private修饰符
            System.out.println("睡" + minutes + "分钟");
        }
    }

    如果不用反射的方式,我相信只要学过java的朋友肯定会调用dinner方法

    Student student = new Student();
    student.dinner();

    如果是反射的方式我们该怎么调用呢?

    //获取Student类信息
    Class cls = Class.forName("com.zimug.java.reflection.Student");
    //对象实例化
    Object obj = cls.getDeclaredConstructor().newInstance();
    //根据方法名获取并执行方法
    Method dinnerMethod = cls.getDeclaredMethod("dinner");
    dinnerMethod.invoke(obj);  //打印:吃晚餐!

    通过上面的代码我们看到,com.zimug.java.reflection.Student类名和dinner方法名是字符串。既然是字符串我们就可以通过配置文件,或数据库、或什么其他的灵活配置方法来执行这段程序了。这就是反射最基础的使用方式。

    三、类加载与反射关系

    java的类加载机制还是挺复杂的,我们这里为了不混淆重点,只为大家介绍和“反射”有关系的一部分内容。

    java执行编译的时候将java文件编译成字节码class文件,类加载器在类加载阶段将class文件加载到内存,并实例化一个java.lang.Class的对象。比如对于Student类在加载阶段会有如下动作:

    • 在内存(方法区或叫代码区)中实例化一个Class对象,注意是Class对象不是Student对象

    • 一个Class类(字节码文件)对应一个Class对象,并且只有一个

    • 该Class对象保存了Student类的基础信息,比如这个Student类有几个字段(Filed)?有几个构造方法(Constructor)?有几个方法(Method)?有哪些注解(Annotation)?等信息

    java反射机制及常用应用场景是什么

    有了上面的关于Student类的基本信息对象(java.lang.Class对象),在运行期就可以根据这些信息来实例化Student类的对象。

    • 在运行期你可以直接new一个Student对象

    • 也可以使用反射的方法构造一个Student对象

    java反射机制及常用应用场景是什么

    但是无论你new多少个Student对象,不论你反射构建多少个Student对象,保存Student类信息的java.lang.Class对象都只有一个。下面的代码可以证明。

    Class cls = Class.forName("com.zimug.java.reflection.Student");
    Class cls2 = new Student().getClass();
    System.out.println(cls == cls2); //比较Class对象的地址,输出结果是true

    四、操作反射的java类

    了解了上面的这些基础信息,我们就可以更深入学习反射类相关的类和方法了:

    java.lang.Class: 代表一个类java.lang.reflect.Constructor: 代表类的构造方法java.lang.reflect.Method: 代表类的普通方法java.lang.reflect.Field: 代表类的成员变量Java.lang.reflect.Modifier: 修饰符,方法的修饰符,成员变量的修饰符。java.lang.annotation.Annotation:在类、成员变量、构造方法、普通方法上都可以加注解

    4.1.获取Class对象的三种方法

    Class.forName()方法获取Class对象

    /**
    * Class.forName方法获取Class对象,这也是反射中最常用的获取对象的方法,因为字符串传参增强了配置实现的灵活性
    */
    Class cls = Class.forName("com.zimug.java.reflection.Student");

    类名.class获取Class对象

    /**
    * `类名.class`的方式获取Class对象
    */
    Class clz = User.class;

    类对象.getClass()方式获取Class对象

    /**
    * `类对象.getClass()`方式获取Class对象
    */
    User user = new User();
    Class clazz = user.getClass();

    虽然有三种方法可以获取某个类的Class对象,但是只有第一种可以被称为“反射”。

    4.2.获取Class类对象的基本信息

    Class cls = Class.forName("com.zimug.java.reflection.Student");
    //获取类的包名+类名
    System.out.println(cls.getName());          //com.zimug.java.reflection.Student
    //获取类的父类
    Class cls = Class.forName("com.zimug.java.reflection.Student");
    //这个类型是不是一个注解?
    System.out.println(cls.isAnnotation());     //false
    //这个类型是不是一个枚举?
    System.out.println(cls.isEnum());      //false
    //这个类型是不是基础数据类型?
    System.out.println(cls.isPrimitive()); //false

    Class类对象信息中几乎包括了所有的你想知道的关于这个类型定义的信息,更多的方法就不一一列举了。还可以通过下面的方法

    获取Class类对象代表的类实现了哪些接口: getInterfaces()

    获取Class类对象代表的类使用了哪些注解: getAnnotations()

    4.3. 获得Class对象的成员变量

    结合上文中的Student类的定义理解下面的代码

    Class cls = Class.forName("com.zimug.java.reflection.Student");
    Field[] fields = cls.getFields();
    for (Field field : fields) {
    System.out.println(field.getName());      //nickName
    }
    fields = cls.getDeclaredFields();
    for (Field field : fields) {
    System.out.println(field.getName());      //nickName 换行  age
    }

    getFields()方法获取类的非私有的成员变量,数组,包含从父类继承的成员变量

    AI Content Detector
    AI Content Detector

    Writer推出的AI内容检测工具

    下载

    getDeclaredFields方法获取所有的成员变量,数组,但是不包含从父类继承而来的成员变量

    4.4.获取Class对象的方法

    getMethods() : 获取Class对象代表的类的所有的非私有方法,数组,包含从父类继承而来的方法

    getDeclaredMethods() : 获取Class对象代表的类定义的所有的方法,数组,但是不包含从父类继承而来的方法

    getMethod(methodName): 获取Class对象代表的类的指定方法名的非私有方法

    getDeclaredMethod(methodName): 获取Class对象代表的类的指定方法名的方法

            Class cls = Class.forName("com.zimug.java.reflection.Student");
            Method[] methods = cls.getMethods();
            System.out.println("Student对象的非私有方法");
            for (Method m : methods) {
                System.out.print(m.getName() + ",");
            }
            System.out.println("  end");
            Method[] allMethods = cls.getDeclaredMethods();
            System.out.println("Student对象的所有方法");
            for (Method m : allMethods) {
                System.out.print(m.getName() + ",");
            }
            System.out.println("  end");
            Method dinnerMethod = cls.getMethod("dinner");
            System.out.println("dinner方法的参数个数" + dinnerMethod.getParameterCount());
            Method sleepMethod = cls.getDeclaredMethod("sleep",int.class);
            System.out.println("sleep方法的参数个数" + sleepMethod.getParameterCount());
            System.out.println("sleep方法的参数对象数组" + Arrays.toString(sleepMethod.getParameters()));
            System.out.println("sleep方法的参数返回值类型" + sleepMethod.getReturnType());

    上面代码的执行结果如下:

    Student对象的非私有方法
    dinner,wait,wait,wait,equals,toString,hashCode,getClass,notify,notifyAll,  end
    Student对象的所有方法
    dinner,sleep,  end
    dinner方法的参数个数0
    sleep方法的参数个数1
    sleep方法的参数对象数组[int arg0]
    sleep方法的参数返回值类型void

    可以看到getMethods获取的方法中包含Object父类中定义的方法,但是不包含本类中定义的私有方法sleep。另外我们还可以获取方法的参数及返回值信息:

    获取参数相关的属性:

    • 获取方法参数个数:getParameterCount()

    • 获取方法参数数组对象:getParameters() ,返回值是java.lang.reflect.Parameter数组

    获取返回值相关的属性

    • 获取方法返回值的数据类型:getReturnType()

    4.5.方法的调用

    实际在上文中已经演示了方法的调用,如下invoke调用dinner方法

    Method dinnerMethod = cls.getDeclaredMethod("dinner");
    dinnerMethod.invoke(obj);  //打印:吃晚餐!

    dinner方法是无参的,那么有参数的方法怎么调用?看看invoke方法定义,第一个参数是Method对象,无论后面Object... args有多少参数就按照方法定义依次传参就可以了。

    public Object invoke(Object obj, Object... args)

    4.6.创建类的对象(实例化对象)

    //获取Student类信息
    Class cls = Class.forName("com.zimug.java.reflection.Student");
    //对象实例化
    Student student = (Student)cls.getDeclaredConstructor().newInstance();
    //下面的这种方法是已经Deprecated了,不建议使用。但是在比较旧的JDK版本中仍然是唯一的方式。
    //Student student = (Student)cls.newInstance();

    五、反射的常用场景

    通过配置信息调用类的方法

    结合注解实现特殊功能

    按需加载jar包或class

    5.1. 通过配置信息调用类的方法

    将上文的hello world中的代码封装一下,你知道类名className和方法名methodName是不是就可以调用方法了?至于你将className和 methodName配置到文件,还是nacos,还是数据库,自己决定吧!

    public void invokeClassMethod(String className,String methodName) throws ClassNotFoundException, 
                NoSuchMethodException, 
                InvocationTargetException, 
                InstantiationException, 
                IllegalAccessException {
            //获取类信息
            Class cls = Class.forName(className);
            //对象实例化
            Object obj = cls.getDeclaredConstructor().newInstance();
            //根据方法名获取并执行方法
            Method dinnerMethod = cls.getDeclaredMethod(methodName);
            dinnerMethod.invoke(obj);
    }

    5.2.结合注解实现特殊功能

    大家如果学习过mybatis plus都应该学习过这样的一个注解TableName,这个注解表示当前的实体类Student对应的数据库中的哪一张表。如下问代码所示,Student所示该类对应的是t_student这张表。

    @TableName("t_student")
    public class Student {
        public String nickName;
        private Integer age;
    }

    下面我们自定义TableName这个注解

    @Target(ElementType.TYPE)  //表示TableName可作用于类、接口或enum Class, 或interface
    @Retention(RetentionPolicy.RUNTIME) //表示运行时由JVM加载
    public @interface TableName {
           String value() ;   //则使用@TableName注解的时候: @TableName(”t_student”);
    }

    有了这个注解,我们就可以扫描某个路径下的java文件,至于类注解的扫描我们就不用自己开发了,引入下面的maven坐标就可以

    
        org.reflections
        reflections
        0.9.10
    

    看下面代码:先扫描包,从包中获取标注了TableName注解的类,再对该类打印注解value信息

    // 要扫描的包
    String packageName = "com.zimug.java.reflection";
    Reflections f = new Reflections(packageName);
    // 获取扫描到的标记注解的集合
    Set> set = f.getTypesAnnotatedWith(TableName.class);
    for (Class c : set) {
    // 循环获取标记的注解
    TableName annotation = c.getAnnotation(TableName.class);
    // 打印注解中的内容
    System.out.println(c.getName() + "类,TableName注解value=" + annotation.value());

    输出结果是:

    com.zimug.java.reflection.Student类,TableName注解value=t_student

    有的朋友会问这有什么用?这有大用处了。有了类定义与数据库表的对应关系,你还能通过反射获取类的成员变量,之后你是不是就可以根据表明t_student和字段名nickName,age构建增删改查的SQL了?全都构建完毕,是不是就是一个基础得Mybatis plus了?

    反射和注解结合使用,可以演化出许许多多的应用场景,特别是在框架代码实现方面。等待你去发觉啊!

    5.3.按需加载jar包或class

    在某些场景下,我们可能不希望JVM的加载器一次性的把所有classpath下的jar包装载到JVM虚拟机中,因为这样会影响项目的启动和初始化效率,并且占用较多的内存。我们希望按需加载,需要用到哪些jar,按照程序动态运行的需求取加载这些jar。

    把jar包放在classpath外面,指定加载路径,实现动态加载。

    //按路径加载jar包
    File file = new File("D:/com/zimug/commons-lang3.jar");
    URL url = file.toURI().toURL();
    //创建类加载器
    ClassLoader classLoader = new URLClassLoader(new URL[]{url});
    Class cls = classLoader.loadClass("org.apache.commons.lang3.StringUtils");

    同样的把.class文件放在一个路径下,我们也是可以动态加载到的

    //java的.class文件所在路径
    File file = new File("D:/com/zimug");
    URL url = file.toURI().toURL();
    //创建类加载器
    ClassLoader classLoader = new URLClassLoader(new URL[]{url});
    //加载指定类,package全路径
    Class cls = classLoader.loadClass("com.zimug.java.reflection.Student");

    类的动态加载能不能让你想到些什么?是不是可以实现代码修改,不需要重新启动web容器?对的,就是这个原理,因为一个类的Class对象只有一个,所以不管你重新加载多少次,都是使用最后一次加载的class对象(上文讲过哦)。

    六、反射的优缺点

    优点:自由,使用灵活,不受类的访问权限限制。可以根据指定类名、方法名来实现方法调用,非常适合实现业务的灵活配置。在框架开发方面也有非常广泛的应用,特别是结合注解的使用。

    缺点:

    • 也正因为反射不受类的访问权限限制,其安全性低,很大部分的java安全问题都是反射导致的。

    • 相对于正常的对象的访问调用,反射因为存在类和方法的实例化过程,性能也相对较低

    • 破坏java类封装性,类的信息隐藏性和边界被破坏

    相关文章

    java速学教程(入门到精通)
    java速学教程(入门到精通)

    java怎么学习?java怎么入门?java在哪学?java怎么学才快?不用担心,这里为大家提供了java速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

    下载

    相关标签:

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

    相关专题

    更多
    Golang gRPC 服务开发与Protobuf实战
    Golang gRPC 服务开发与Protobuf实战

    本专题系统讲解 Golang 在 gRPC 服务开发中的完整实践,涵盖 Protobuf 定义与代码生成、gRPC 服务端与客户端实现、流式 RPC(Unary/Server/Client/Bidirectional)、错误处理、拦截器、中间件以及与 HTTP/REST 的对接方案。通过实际案例,帮助学习者掌握 使用 Go 构建高性能、强类型、可扩展的 RPC 服务体系,适用于微服务与内部系统通信场景。

    8

    2026.01.15

    公务员递补名单公布时间 公务员递补要求
    公务员递补名单公布时间 公务员递补要求

    公务员递补名单公布时间不固定,通常在面试前,由招录单位(如国家知识产权局、海关等)发布,依据是原入围考生放弃资格,会按笔试成绩从高到低递补,递补考生需按公告要求限时确认并提交材料,及时参加面试/体检等后续环节。要求核心是按招录单位公告及时响应、提交材料(确认书、资格复审材料)并准时参加面试。

    44

    2026.01.15

    公务员调剂条件 2026调剂公告时间
    公务员调剂条件 2026调剂公告时间

    (一)符合拟调剂职位所要求的资格条件。 (二)公共科目笔试成绩同时达到拟调剂职位和原报考职位的合格分数线,且考试类别相同。 拟调剂职位设置了专业科目笔试条件的,专业科目笔试成绩还须同时达到合格分数线,且考试类别相同。 (三)未进入原报考职位面试人员名单。

    58

    2026.01.15

    国考成绩查询入口 国考分数公布时间2026
    国考成绩查询入口 国考分数公布时间2026

    笔试成绩查询入口已开通,考生可登录国家公务员局中央机关及其直属机构2026年度考试录用公务员专题网站http://bm.scs.gov.cn/pp/gkweb/core/web/ui/business/examResult/written_result.html,查询笔试成绩和合格分数线,点击“笔试成绩查询”按钮,凭借身份证及准考证进行查询。

    11

    2026.01.15

    Java 桌面应用开发(JavaFX 实战)
    Java 桌面应用开发(JavaFX 实战)

    本专题系统讲解 Java 在桌面应用开发领域的实战应用,重点围绕 JavaFX 框架,涵盖界面布局、控件使用、事件处理、FXML、样式美化(CSS)、多线程与UI响应优化,以及桌面应用的打包与发布。通过完整示例项目,帮助学习者掌握 使用 Java 构建现代化、跨平台桌面应用程序的核心能力。

    65

    2026.01.14

    php与html混编教程大全
    php与html混编教程大全

    本专题整合了php和html混编相关教程,阅读专题下面的文章了解更多详细内容。

    36

    2026.01.13

    PHP 高性能
    PHP 高性能

    本专题整合了PHP高性能相关教程大全,阅读专题下面的文章了解更多详细内容。

    75

    2026.01.13

    MySQL数据库报错常见问题及解决方法大全
    MySQL数据库报错常见问题及解决方法大全

    本专题整合了MySQL数据库报错常见问题及解决方法,阅读专题下面的文章了解更多详细内容。

    21

    2026.01.13

    PHP 文件上传
    PHP 文件上传

    本专题整合了PHP实现文件上传相关教程,阅读专题下面的文章了解更多详细内容。

    35

    2026.01.13

    热门下载

    更多
    网站特效
    /
    网站源码
    /
    网站素材
    /
    前端模板

    精品课程

    更多
    相关推荐
    /
    热门推荐
    /
    最新课程
    Kotlin 教程
    Kotlin 教程

    共23课时 | 2.5万人学习

    C# 教程
    C# 教程

    共94课时 | 6.8万人学习

    Java 教程
    Java 教程

    共578课时 | 46.2万人学习

    关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
    php中文网:公益在线php培训,帮助PHP学习者快速成长!
    关注服务号 技术交流群
    PHP中文网订阅号
    每天精选资源文章推送

    Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号