Java之Java基础十六(反射)

一、什么是反射

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

Writer writer = new Writer();
writer.setName("aaa");

像上面这个例子,就可以理解为“正射”。而反射就意味着一开始我们不知道要初始化的类到底是什么,也就没法直接使用 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,"aaa");

像上面这个例子,就可以理解为“反射”。

反射的写法比正射复杂得多,反射的成本是要比正射的高得多,

反射的缺点主要有两个

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

 那反射有哪些好处呢?

反射的主要应用场景有:

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

二、反射过程

来看一下完整的例子吧

Writer 类,有两个字段,然后还有对应的 getter/setter。

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("aaa");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, "aaa");Method getNameMethod = clazz.getMethod("getName");System.out.println(getNameMethod.invoke(object));}
}

来看一下输出结果:

aaa

aaa

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

第一步,获取反射类的 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, "aaa");
getNameMethod.invoke(object)

经过这五个步骤,基本上就掌握了反射的使用方法。掌握反射的基本使用方法确实不难,但要理解整个反射机制还是需要花一点时间去了解一下 Java 虚拟机的类加载机制的。

要想使用反射,首先需要获得反射类的 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,最后再调用实际的方法。

为什么不直接调用本地实现呢?

之所以采用委派实现,是为了能够在本地实现和动态实现之间切换。动态实现是另外一种反射调用机制,它是通过生成字节码的形式来实现的。如果反射调用的次数比较多,动态实现的效率就会更高,因为本地实现需要经过 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 即可。

Field[] fields1 = System.class.getFields();
Field fields2 = System.class.getField("out");

(5)获取方法

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

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

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

四、总结

反射是 Java 中的一个强大特性,它允许在运行时检查和操作类、接口、字段和方法。反射是 Java 的核心组件,支持各种框架和库的实现,如 Spring、Hibernate 等。使用反射,可以在运行时动态地创建对象、调用方法和访问字段,而无需在编译时了解这些对象的具体实现。

反射的主要类位于 java.lang.reflect 包中,主要包括以下几个关键类:

  • Class:代表一个类或接口,包含了类的结构信息(如名称、构造函数、方法、字段等)。通过 Class 对象,可以获取类的元数据并操作类的实例。
  • Constructor:代表类的构造方法,用于创建类的实例。
  • Method:代表类的方法,可以通过它调用类的实例方法。
  • Field:代表类的字段,可以获取或修改字段的值。
  • Modifier:包含方法、字段和类的访问修饰符。

使用反射时,需要注意以下几点:

  • 性能:反射操作通常比直接操作对象的方法和字段慢,因为涉及到额外的间接调用和动态解析。因此,在关注性能的场景中,慎用反射。
  • 安全性:通过反射,可以访问和操作类的私有字段和方法,这可能导致安全问题。因此,使用反射时要确保代码的安全性。
  • 维护性:反射使代码变得更加复杂,可能导致难以维护。在使用反射时要确保代码的可读性和可维护性。

尽管反射存在上述问题,但在某些场景下(如框架开发、动态代理等),它仍然是非常有用的工具。

参考链接:https://javabetter.cn/basic-extra-meal/fanshe.html

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

WPF的MVVM架构:如何通过数据绑定简化UI逻辑

WPF的MVVM架构&#xff1a;如何通过数据绑定简化UI逻辑 目录 MVVM模式概述数据绑定在MVVM中的作用实现MVVM模式的步骤MVVM模式中的常见问题与解决方案实践示例总结 MVVM模式概述 MVVM&#xff08;Model-View-ViewModel&#xff09;是一种设计模式&#xff0c;用于WPF应用程序…

超声波传感器 - 从零开始认识各种传感器【第十九期】

超声波传感器|从零开始认识各种传感器 1、什么是超声波传感器 超声波传感器是一种利用超声波来进行距离测量和目标检测的传感器。它通过发送&#xff0c;超声波脉冲&#xff0c;并测量超声波从传感器发射到目标物体并返回的时间来计算目标物体与传感器之间的距离。 超声波传感…

echarts无数据的展示内容,用graphic属性配置

echarts无数据的展示内容&#xff0c;用graphic属性配置 当echarts无数据的时候&#xff0c;图表展示的是个空白部分&#xff0c;感觉会有点丑&#xff0c;影响页面美观&#xff0c;这时候翻阅了echarts的官网&#xff0c;让我找到个配置项&#xff0c;试试发现还可以&#xf…

Notion支持直接绑定自己的域名,有何工具可替代为公开网站自定义域名?

Notion最近大招频出&#xff0c;推出新功能——自定义域名。只需简单几步&#xff0c;xxx.notion.site秒变你的专属域名&#xff08;月费仅需10美金&#xff09;。推特上的独立内容创作者/初创公司&#xff0c;用它来打造品牌、分享资料模板&#xff0c;甚至实现盈利。 Notion的…

你还在为PDF转Word烦恼?试试这四款免费工具吧!

悄咪咪问一句&#xff0c;大家在平时上班时最头疼的事情有哪些&#xff1f;我想会有很多朋友也有pdf如何在线转换word文档的免费方式&#xff0c;毕竟这些办公文档是非常常见的问题了&#xff0c;所以今天就专门准备这么一篇文章来分享我个人喜欢的四款好用工具&#xff1a; 第…

做知识付费项目还能做吗?知识付费副业项目如何做?能挣多少钱?

hello,我是阿磊&#xff0c;一个20年的码农&#xff0c;6年前代码写不动了&#xff0c;转型专职做副业项目研究&#xff0c;为劳苦大众深度挖掘互联网副业项目&#xff0c;共同富裕。 现在做知识付费项目还能做吗&#xff1f; 互联网虚拟资源项目我一直在做&#xff0c;做了有…

【单片机毕业设计选题24088】-基于STM32的智能家居控制系统

系统功能: 系统操作说明&#xff1a; 上电后OLED显示 “欢迎使用智能家居系统请稍后”&#xff0c;两秒后显示Connecting...表示 正在连接阿里云&#xff0c;正常连接阿里云后显示第一页面&#xff0c;如长时间显示Connecting...请 检查WiFi网络是否正确。 第一页面第一行…

使用runlink通过容器打印出容器的启动命令

1、Runlike简介 Runlike:通过容器打印出容器的启动命令&#xff0c;然后发现自己需要手动重新运行一些容器的人来说&#xff0c;这是一个真正的节省时间。 2、Docker镜像安装 2.1 构建Runlike容器 [rootlocalhost ~]# docker run --rm -v /var/run/docker.sock:/var/run/do…

嵌入式Linux:符号链接(软链接)和硬链接

目录 1、符号链接&#xff08;软链接&#xff09; 2、硬链接 3、link()函数 4、symlink()函数 5、readlink()函数 在 Linux 系统中&#xff0c;符号链接&#xff08;软链接&#xff09;和硬链接是两种创建文件链接的方法。理解它们的区别和使用场景对于文件系统的管理非常…

Spring核心机制Ioc和Aop

Spring全家桶 WEB&#xff1a;SpringMvc、Spring Web Flux 持久层&#xff1a;Spring Data、Spring Data Redis、Spring Data MongoDB 安全校验&#xff1a;spring Security 构建工程脚手架&#xff1a;SpringBoot 微服务&#xff1a;SpringCloud 所有的Spring框架集成&#xf…

轻松入门Linux—CentOS,直接拿捏 —/— <1>

一、什么是Linux Linux是一个开源的操作系统&#xff0c;目前是市面上占有率极高的服务器操作系统&#xff0c;目前其分支有很多。是一个基于 POSIX 和 UNIX 的多用户、多任务、支持多线程和多 CPU 的操作系统 Linux能运行主要的UNIX工具软件、应用程序和网络协议 Linux支持 32…

每日OJ_牛客CM26 二进制插入

目录 牛客CM26 二进制插入 解析代码 牛客CM26 二进制插入 二进制插入_牛客题霸_牛客网 解析代码 m:1024&#xff1a;100000000 00 n:19 &#xff1a; 10011 要把n的二进制值插入m的第j位到第i位&#xff0c;只需要把n先左移j位&#xff0c;然后再进行或运算&#xff08;|&am…

ctfshow 权限维持 web670--web679

web670 <?php// 题目说明&#xff1a; // 想办法维持权限&#xff0c;确定无误后提交check&#xff0c;通过check后&#xff0c;才会生成flag&#xff0c;此前flag不存在error_reporting(0); highlight_file(__FILE__);$a$_GET[action];switch($a){case cmd:eval($_POST[c…

OCC BRepOffsetAPI_ThruSections使用

目录 一、BRepOffsetAPI_ThruSections简介 二、功能与特点 三、应用场景 四、示例 一、BRepOffsetAPI_ThruSections简介 在Open CASCADE Technology (OCCT) 中,BRepOffsetAPI_ThruSections 类是用来通过放样生成一个实体或者一个面壳(Shell)。当使用这个类时,isSolid 参…

【环境搭建问题】linux服务器安装conda并创建虚拟环境

1.检查有没有conda 首先看root文件夹下有没有anaconda或者conda 没有的话就要先下载安装conda&#xff1a; https://repo.anaconda.com/archive/index.html 在这个链接下找自己需要的。服务器一般为linux&#xff0c;所以我这里选择的是&#xff1a; 2.安装conda 下载安装…

Electron学习笔记(二)Hello World

目录 前言 运行主进程 创建界面 使用窗口打开界面 管理窗口的生命周期 关闭所有窗口时退出应用 (Windows & Linux)​ 如果没有窗口打开则打开一个窗口 (macOS) 使用预加载脚本访问渲染器的Node.js 添加你自己的功能 完整代码展示 效果展示 前言 接上一篇文章 …

LINUX进程间的通信(IPC)--信号

一、概念 信号通信&#xff0c;其实就是内核向用户空间进程发送信号&#xff0c;只有内核才能发信号&#xff0c;用户空间进程不能发送信号。信号已经是存在内核中的了&#xff0c;不需要用户自己创建。 信号通信的框架 * 信号的发送&#xff08;发送信号进程&#xff09;&am…

JS图形引擎汇总

1、leaferjs leaferjs绚丽多彩的 HTML5 Canvas 2D 图形渲染引擎&#xff0c;可结合 AI 绘图、生成界面。 提供了丰富的 UI 绘图元素&#xff0c;和开箱即用的功能&#xff0c;如自动布局、图形编辑、SVG 导出&#xff0c;方便与 Figma、Sketch 等产品进行数据交换。并为跨平台…

示波器选择导出至USB闪存盘的三种格式(bmp、set、csv)

如下图所示&#xff0c;一般由示波器导出至U盘中&#xff0c;一共有三种文件格式。 1、当前屏幕图像(*.bmp) BMP 文件格式&#xff08;全称是位图文件格式&#xff0c;Bitmap Image File&#xff09;是一种图像文件格式&#xff0c;用于存储数字图像。它是一种无损图像格式&am…

m3u8转MP4(网页视频下载)(超细教程, 有手就行)

准备工作 什么是m3u8文件 记事本打开m3u8文件&#xff1a; #EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:4 #EXT-X-MEDIA-SEQUENCE:0 #EXT-X-KEY:METHODAES-128,URI"https://xxxx/key.key" #EXTINF:3.336667, #EXTINF:1.668333, clvHz13123499.ts #EXTINF:2.03…