Java 反射详解:动态创建实例、调用方法和访问字段

“一般情况下,我们在使用某个类之前已经确定它到底是个什么类了,拿到手就直接可以使用 new 关键字来调用构造方法进行初始化,之后使用这个类的对象来进行操作。”

Writer writer = new Writer();
writer.setName("少年");

像上面这个例子,就可以理解为“正射”。而反射就意味着一开始我们不知道要初始化的类到底是什么,也就没法直接使用 new 关键字创建对象了。

我们只知道这个类的一些基本信息,就好像我们看电影的时候,为了抓住一个犯罪嫌疑人,警察就会问一些目击证人,根据这些证人提供的信息,找专家把犯罪嫌疑人的样貌给画出来——这个过程,就可以称之为反射。

Class clazz = Class.forName("com.itwanger.s39.Writer");
Method method = clazz.getMethod("setName", String.class);
Constructor constructor = clazz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object,"少年");

像上面这个例子,就可以理解为“反射”。“反射的缺点主要有两个。”

  • 破坏封装:由于反射允许访问私有字段和私有方法,所以可能会破坏封装而导致安全问题。
  • 性能开销:由于反射涉及到动态解析,因此无法执行 Java 虚拟机优化,再加上反射的写法的确要复杂得多,所以性能要比“正射”差很多,在一些性能敏感的程序中应该避免使用反射。

反射的主要应用场景有:

  • 开发通用框架:像 Spring,为了保持通用性,通过配置文件来加载不同的对象,调用不同的方法。
  • 动态代理:在面向切面编程中,需要拦截特定的方法,就会选择动态代理的方式,而动态代理的底层技术就是反射。
  • 注解:注解本身只是起到一个标记符的作用,它需要利用发射机制,根据标记符去执行特定的行为。

举例:

public class Writer {private int age;private String name;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;}
}

测试类:

public class ReflectionDemo1 {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {Writer writer = new Writer();writer.setName("少年");System.out.println(writer.getName());Class clazz = Class.forName("com.itwanger.s39.Writer");Constructor constructor = clazz.getConstructor();Object object = constructor.newInstance();Method setNameMethod = clazz.getMethod("setName", String.class);setNameMethod.invoke(object, "少年");Method getNameMethod = clazz.getMethod("getName");System.out.println(getNameMethod.invoke(object));}
}

结果:

少年
少年

只不过,反射的过程略显曲折了一些。

第一步,获取反射类的 Class 对象:

 Class clazz = Class.forName("com.itwanger.s39.Writer");

在 Java 中,Class 对象是一种特殊的对象,它代表了程序中的类和接口。

Java 中的每个类型(包括类、接口、数组以及基础类型)在 JVM 中都有一个唯一的 Class 对象与之对应。这个 Class 对象被创建的时机是在 JVM 加载类时,由 JVM 自动完成。

Class 对象中包含了与类相关的很多信息,如类的名称、类的父类、类实现的接口、类的构造方法、类的方法、类的字段等等。这些信息通常被称为元数据(metadata)。

除了前面提到的,通过类的全名获取 Class 对象,还有以下两种方式:

  • 如果你有一个类的实例,你可以通过调用该实例的getClass()方法获取 Class 对象。例如:String str = “Hello World”; Class cls = str.getClass();
  • 如果你有一个类的字面量(即类本身),你可以直接获取 Class 对象。例如:Class cls = String.class;
    第二步,通过 Class 对象获取构造方法 Constructor 对象:
Constructor constructor = clazz.getConstructor();

第三步,通过 Constructor 对象初始化反射类对象:

Object object = constructor.newInstance();

第四步,获取要调用的方法的 Method 对象:

Method setNameMethod = clazz.getMethod("setName", String.class);
Method getNameMethod = clazz.getMethod("getName");

第五步,通过 invoke() 方法执行:

setNameMethod.invoke(object, "少年");
getNameMethod.invoke(object)

要想使用反射,首先需要获得反射类的 Class 对象,每一个类,不管它最终生成了多少个对象,这些对象只会对应一个 Class 对象,这个 Class 对象是由 Java 虚拟机生成的,由它来获悉整个类的结构信息。

也就是说,java.lang.Class 是所有反射 API 的入口。

而方法的反射调用,最终是由 Method 对象的 invoke() 方法完成的,来看一下源码(JDK 8 环境下)。

public Object invoke(Object obj, Object... args)throws IllegalAccessException, IllegalArgumentException,InvocationTargetException {// 如果方法不允许被覆盖,进行权限检查if (!override) {if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {Class<?> caller = Reflection.getCallerClass();// 检查调用者是否具有访问权限checkAccess(caller, clazz, obj, modifiers);}}// 获取方法访问器(从 volatile 变量中读取)MethodAccessor ma = methodAccessor;if (ma == null) {// 如果访问器为空,尝试获取方法访问器ma = acquireMethodAccessor();}// 使用方法访问器调用方法,并返回结果return ma.invoke(obj, args);
}

两个嵌套的 if 语句是用来进行权限检查的。

invoke() 方法实际上是委派给 MethodAccessor 接口来完成的。

在这里插入图片描述
MethodAccessor 接口有三个实现类,其中的 MethodAccessorImpl 是一个抽象类,另外两个具体的实现类继承了这个抽象类。
在这里插入图片描述

  • NativeMethodAccessorImpl:通过本地方法来实现反射调用;
  • DelegatingMethodAccessorImpl:通过委派模式来实现反射调用;
    通过 debug 的方式进入 invoke() 方法后,可以看到第一次反射调用会生成一个委派实现 DelegatingMethodAccessorImpl,它在生成的时候会传递一个本地实现 NativeMethodAccessorImpl。
    在这里插入图片描述

也就是说,invoke() 方法在执行的时候,会先调用 DelegatingMethodAccessorImpl,然后调用 NativeMethodAccessorImpl,最后再调用实际的方法。

也就是说,invoke() 方法在执行的时候,会先调用 DelegatingMethodAccessorImpl,然后调用 NativeMethodAccessorImpl,最后再调用实际的方法。

“之所以采用委派实现,是为了能够在本地实现和动态实现之间切换。动态实现是另外一种反射调用机制,它是通过生成字节码的形式来实现的。如果反射调用的次数比较多,动态实现的效率就会更高,因为本地实现需要经过 Java 到 C/C++ 再到 Java 之间的切换过程,而动态实现不需要;但如果反射调用的次数比较少,反而本地实现更快一些。
那临界点是多少呢?“默认是 15 次。“可以通过 -Dsun.reflect.inflationThreshold 参数类调整。”

来看下面这个例子。

Method setAgeMethod = clazz.getMethod("setAge", int.class);
for (int i = 0;i < 20; i++) {setAgeMethod.invoke(object, 18);
}

在 invoke() 方法处加断点进入 debug 模式,当 i = 15 的时候,也就是第 16 次执行的时候,会进入到 if 条件分支中,改变 DelegatingMethodAccessorImpl 的委派模式 delegate 为 (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(),而之前的委派模式 delegate 为 NativeMethodAccessorImpl。
在这里插入图片描述
“接下来,我们再来熟悉一下反射当中常用的 API。”

1)获取反射类的 Class 对象
Class.forName(),参数为反射类的完全限定名

Class c1 = Class.forName("com.itwanger.s39.ReflectionDemo3");
System.out.println(c1.getCanonicalName());Class c2 = Class.forName("[D");
System.out.println(c2.getCanonicalName());Class c3 = Class.forName("[[Ljava.lang.String;");
System.out.println(c3.getCanonicalName());

输出结果:

com.itwanger.s39.ReflectionDemo3
double[]
java.lang.String[][]

类名 + .class,只适合在编译前就知道操作的 Class。

Class c1 = ReflectionDemo3.class;
System.out.println(c1.getCanonicalName());Class c2 = String.class;
System.out.println(c2.getCanonicalName());Class c3 = int[][][].class;
System.out.println(c3.getCanonicalName());

来看一下输出结果:

com.itwanger.s39.ReflectionDemo3
java.lang.String
int[][][]

2)创建反射类的对象

  • 用 Class 对象的 newInstance() 方法。
  • 用 Constructor 对象的 newInstance() 方法
Class c1 = Writer.class;
Writer writer = (Writer) c1.newInstance();Class c2 = Class.forName("com.itwanger.s39.Writer");
Constructor constructor = c2.getConstructor();
Object object = constructor.newInstance();

3)获取构造方法

Class 对象提供了以下方法来获取构造方法 Constructor 对象:

  • getConstructor():返回反射类的特定 public 构造方法,可以传递参数,参数为构造方法参数对应 Class 对象;缺省的时候返回默认构造方法。
  • getDeclaredConstructor():返回反射类的特定构造方法,不限定于 public 的。
  • getConstructors():返回类的所有 public 构造方法。
  • getDeclaredConstructors():返回类的所有构造方法,不限定于 public 的。
Class c2 = Class.forName("com.itwanger.s39.Writer");
Constructor constructor = c2.getConstructor();Constructor[] constructors1 = String.class.getDeclaredConstructors();
for (Constructor c : constructors1) {System.out.println(c);
}

4)获取字段

大体上和获取构造方法类似,把关键字 Constructor 换成 Field 即可。

Method setNameMethod = clazz.getMethod("setName", String.class);
Method getNameMethod = clazz.getMethod("getName");

5)获取方法

大体上和获取构造方法类似,把关键字 Constructor 换成 Method 即可。

Method[] methods1 = System.class.getDeclaredMethods();
Method[] methods2 = System.class.getMethods();

注意,如果想反射访问私有字段和(构造)方法的话,需要使用 Constructor/Field/Method.setAccessible(true) 来绕开 Java 语言的访问限制。

参考:https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/269561.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

no declaration can be found for element ‘rabbit:connection-factory‘

spring-mvc 配置 rabbitmq 出现问题。 我的解决方案如下&#xff1a; 1 找到配置文件 spring-rabbitmq.xml 我的配置文件叫&#xff1a;spring-rabbitmq.xml&#xff0c;你们按照自己的查找。 2 定位如下URI 接着 Ctrl鼠标左键 3 确定spring-rabbit-x.x.xsd 按照步骤2 &…

鸿蒙Harmony应用开发—ArkTS声明式开发(通用属性:组件标识)

id为组件的唯一标识&#xff0c;在整个应用内唯一。本模块提供组件标识相关接口&#xff0c;可以获取指定id组件的属性&#xff0c;也提供向指定id组件发送事件的功能。 说明&#xff1a; 从API Version 8开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容…

C++基于多设计模式下的同步异步日志系统day7(终)

C基于多设计模式下的同步&异步日志系统day7&#xff08;终&#xff09; &#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;C基于多设计模式下的同步&异步日志系统 &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#…

美易官方《盘前:美国股指期货温和走低》

美国股指期货在盘前交易中温和走低&#xff0c;市场情绪在美联储主席鲍威尔即将作证前显得谨慎。投资者对即将公布的证词内容充满期待&#xff0c;以寻求对美联储未来货币政策的更多线索。 鲍威尔即将在国会作证&#xff0c;这是市场关注的焦点事件之一。他的证词可能会对美元汇…

字节同事问我:我的Postman为什么连不了数据库?

postman本身没有数据库连接功能&#xff0c;所以用到了node.js中的xmysql实现Rest API的生成&#xff0c;利用postman进行请求&#xff0c;获取需要的数据&#xff0c;来做数据准备或断言。 1 安装 安装node.js&#xff1a;要求版本大于等于7.6 首先保证你的环境上有node.js…

【NR 定位】3GPP NR Positioning 5G定位标准解读(四)

目录 前言 6 Signalling protocols and interfaces 6.1 支持定位操作的网络接口 6.1.1 通用LCS控制平面架构 6.1.2 NR-Uu接口 6.1.3 LTE-Uu接口 6.1.4 NG-C接口 6.1.5 NL1接口 6.1.6 F1接口 6.1.7 NR PC5接口 6.2 终端协议 6.2.1 LTE定位协议&#xff08;LPP&#x…

贪心算法(区间问题)

452. 用最少数量的箭引爆气球 题目(求无重复区间) 有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points &#xff0c;其中points[i] [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。 一支弓箭可以沿着…

爬虫入门到精通_实战篇11(使用代理处理反爬抓取微信文章)_PyQuery使用

1 目标 搜狗-微信这个网址来爬取微信的文章&#xff1a; ps&#xff1a;登录后才能查看第10页之后的内容&#xff1a; 量翻页触发了网站的反爬虫措施&#xff0c;导致ip被封&#xff0c;需要进行解锁。 然而从doc中可以看到&#xff0c;请求失败的那页&#xff08;状态码应…

【力扣白嫖日记】1045.买下所有产品的客户

前言 练习sql语句&#xff0c;所有题目来自于力扣&#xff08;https://leetcode.cn/problemset/database/&#xff09;的免费数据库练习题。 今日题目&#xff1a; 1045.买下所有产品的客户 表&#xff1a;Customer 列名类型customer_idintproduct_keyint 该表可能包含重复…

【Vue3】深入理解Vue中的ref属性

&#x1f497;&#x1f497;&#x1f497;欢迎来到我的博客&#xff0c;你将找到有关如何使用技术解决问题的文章&#xff0c;也会找到某个技术的学习路线。无论你是何种职业&#xff0c;我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章&#xff0c;也欢…

MyCAT集群——MyCAT2如何配置读写分离

先搭载MySQL一主两从 192.168.20.110MyCAT192.168.20.111Master192.168.20.112slave1192.168.20.113slave2 配置就不写了&#xff0c;比较基础&#xff0c;写一下步骤 1.进入mysql配置文件或者其子配置文件&#xff0c;添加server_id,开启gtidgtid_modeON,enforce-gtid-cons…

个人社区 项目测试

目 录 一.背景及介绍二.功能详情三.手动测试1.编写测试用例2.测试 一.背景及介绍 该项目采用了前后端分离技术&#xff0c;把我们的数据保存到数据库中&#xff0c;操作对象是用户和个人文章编辑保存&#xff0c;前端的页面实现了登录&#xff0c;列表&#xff0c;编辑&#x…

学习JAVA的第十三天(基础)

目录 API之Arrays 将数组变成字符串 二分查找法查找元素 拷贝数组 填充数组 排序数组 Lambda表达式 集合的进阶 单列集合 体系结构 Collection API之Arrays 操作数组的工具类 将数组变成字符串 //将数组变成字符串char[] arr {a,b,c,d,e};System.out.println(Arra…

leetcode--接雨水(双指针法,动态规划,单调栈)

目录 方法一&#xff1a;双指针法 方法二&#xff1a;动态规划 方法三&#xff1a;单调栈 42. 接雨水 - 力扣&#xff08;LeetCode&#xff09; 黑色的是柱子&#xff0c;蓝色的是雨水&#xff0c;我们先来观察一下雨水的分布情况: 雨水落在凹槽之间&#xff0c;在一个凹槽的…

第十三届蓝桥杯嵌入式省赛程序设计详细题解

第十三届蓝桥杯嵌入式省赛题目相对于第十二届较为简单&#xff0c;没有那么多串口的数据处理以及判断&#xff01; 第十三届省赛主要是制作一个可由串口设置密码的密码锁。本实验中&#xff0c;我们将用到LED模块、按键模块、串口模块、定时器的PWM模块以及官方会提供源码的LC…

01、MongoDB -- 下载、安装、配置文件等配置 及 副本集配置

目录 MongoDB -- 下载、安装、配置 及 副本集配置启动命令启动 mongodb 的服务器&#xff08;单机和副本集&#xff09;启动单机模式的 mongodb 服务器启动副本集的 3 个副本节点&#xff08;mongodb 服务器&#xff09; 启动 mongodb 的客户端 MongoDB 下载MongoDB 安装1、解压…

UE5 UE4 不同关卡使用Sequence动画

参考自&#xff1a;关于Datasmith导入流程 | 虚幻引擎文档 (unrealengine.com) 关卡中的Sequence动画序列&#xff0c;包含特定关卡中的Actor的引用。 将同一个Sequcen动画资源放入其他关卡&#xff0c;Sequence无法在新关卡中找到相同的Actor&#xff0c;导致报错。 Sequen…

android Service 与 activity 通信 并不断传数据

注&#xff1a;这只是个Demo 以下载为案例&#xff0c;实现开启下载&#xff0c;暂停下载&#xff0c;下载进度不断发送给activity class DownloadService : Service() {override fun onBind(intent: Intent?): IBinder? {return MyBinder()}inner class MyBinder : Binder…

Java知识点总结(二)

ID生成策略 主键自增id 主键自动增长&#xff0c;不用手工设值、数字型&#xff0c;占用空间小、检索非常有利、有顺序&#xff0c;不会重复&#xff0c;但在迁移旧数据是会出现id冲突 UUID 基于时间&#xff0c;计数器和地址生成32位的id redis生成id 原子性自增&#xff0c;并…

Flask入门一(介绍、Flask安装、Flask运行方式及使用、虚拟环境、调试模式、配置文件、路由系统)

文章目录 一、Flask介绍二、Flask创建和运行1.安装2.快速使用3.Flask小知识4.flask的运行方式 三、Werkzeug介绍四、Jinja2介绍五、Click CLI 介绍六、Flask安装介绍watchdog使用python--dotenv使用&#xff08;操作环境变量&#xff09; 七、虚拟环境介绍Mac/linux创建虚拟环境…