34. 【Java教程】反射

本小节我们来学习一个 Java 语言中较为深入的概念 —— 反射(reflection),很多小伙伴即便参与了工作,可能也极少用到 Java 反射机制,但是如果你想要开发一个 web 框架,反射是不可或缺的知识点。本小节我们将了解到 什么是反射反射的使用场景,不得不提的 Class 类,如何通过反射访问类内部的字段、方法以及构造方法等知识点。

1. 什么是反射

Java 的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为 Java 语言的反射机制。反射被视为动态语言的关键。

通常情况下,我们想调用一个类内部的属性或方法,需要先实例化这个类,然后通过对象去调用类内部的属性和方法;通过 Java 的反射机制,我们就可以在程序的运行状态中,动态获取类的信息,注入类内部的属性和方法,完成对象的实例化等操作。

概念可能比较抽象,我们来看一下结合示意图看一下:

图中解释了两个问题:

  1. 程序运行状态中指的是什么时刻Hello.java 源代码文件经过编译得到 Hello.class 字节码文件,想要运行这个程序,就要通过 JVM 的 ClassLoader (类加载器)加载 Hello.class,然后 JVM 来运行 Hello.class,程序的运行期间指的就是此刻;
  2. 什么是反射,它有哪些功能:在程序运行期间,可以动态获得 Hello 类中的属性和方法、动态完成 Hello 类的对象实例化等操作,这个功能就称为反射。

说到这里,大家可能觉得,在编写代码时直接通过 new 的方式就可以实例化一个对象,访问其属性和方法,为什么偏偏要绕个弯子,通过反射机制来进行这些操作呢?下面我们就来看一下反射的使用场景。

2. 反射的使用场景

Java 的反射机制,主要用来编写一些通用性较高的代码或者编写框架的时候使用。

通过反射的概念,我们可以知道,在程序的运行状态中,对于任意一个类,通过反射都可以动态获取其信息以及动态调用对象。

例如,很多框架都可以通过配置文件,来让开发者指定使用不同的类,开发者只需要关心配置,不需要关心代码的具体实现,具体实现都在框架的内部,通过反射就可以动态生成类的对象,调用这个类下面的一些方法。

下面的内容,我们将学习反射的相关 API,在本小节的最后,我将分享一个自己实际开发中的反射案例。

3. 反射常用类概述

学习反射就需要了解反射相关的一些类,下面我们来看一下如下这几个类:

  • ClassClass 类的实例表示正在运行的 Java 应用程序中的类和接口;
  • Constructor:关于类的单个构造方法的信息以及对它的权限访问;
  • Field:Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限;
  • Method:Method 提供关于类或接口上单独某个方法的信息。

字节码文件想要运行都是要被虚拟机加载的,每加载一种类,Java 虚拟机都会为其创建一个 Class 类型的实例,并关联起来。

例如,我们自定义了一个 MybjStudent.java 类,类中包含有构造方法、成员属性、成员方法等:

public class MybjStudent {// 无参构造方法public MybjStudent() {}// 有参构造方法public MybjStudent(String nickname) {this.nickname = nickname;}// 昵称private String nickname;// 定义getter和setter方法public String getNickname() {return nickname;}public void setNickname(String nickname) {this.nickname = nickname;}
}

源码文件 MybjStudent.java 会被编译器编译成字节码文件 MybjStudent.class,当 Java 虚拟机加载这个 MybjStudent.class 的时候,就会创建一个 Class 类型的实例对象:

Class cls = new Class(MybjStudent);

JVM 为我们自动创建了这个类的对象实例,因此就可以获取类内部的构造方法、属性和方法等 MybjStudent 的构造方法就称为 Constructor,可以创建对象的实例,属性就称为 Field,可以为属性赋值,方法就称为 Method,可以执行方法。

4. Class 类

4.1 Class 类和 class 文件的关系

java.lang.Class 类用于表示一个类的字节码(.class)文件。

4.2 获取 Class 对象的方法

想要使用反射,就要获取某个 class 文件对应的 Class 对象,我们有 3 种方法:

  1. 类名.class:即通过一个 Class 的静态变量 class 获取,实例如下:
Class cls = MybjStudent.class;
  1. 对象.getClass ():前提是有该类的对象实例,该方法由 java.lang.Object 类提供,实例如下:
MybjStudent mybjStudent = new MybjStudent("小码");
Class mybjStudent.getClass();
  1. Class.forName (“包名。类名”):如果知道一个类的完整包名,可以通过 Class 类的静态方法 forName() 获得 Class 对象,实例如下:
class cls = Class.forName("java.util.ArrayList");

4.3 实例

package com.mybj.reflect;public class MybjStudent {// 无参构造方法public MybjStudent() {}// 有参构造方法public MybjStudent(String nickname) {this.nickname = nickname;}// 昵称private String nickname;// 定义getter和setter方法public String getNickname() {return nickname;}public void setNickname(String nickname) {this.nickname = nickname;}public static void main(String[] args) throws ClassNotFoundException {// 方法1:类名.classClass cls1 = MybjStudent.class;// 方法2:对象.getClass()MybjStudent student = new MybjStudent();Class cls2 = student.getClass();// 方法3:Class.forName("包名.类名")Class cls3 = Class.forName("com.mybj.reflect.MybjStudent");}}

代码中,我们在 com.mybj.reflect 包下定义了一个 MybjStudent 类,并在主方法中,使用了 3 种方法获取 Class 的实例对象,其 forName() 方法会抛出一个 ClassNotFoundException

4.4 调用构造方法

获取了 Class 的实例对象,我们就可以获取 Contructor 对象,调用其构造方法了。

那么如何获得 Constructor 对象?Class 提供了以下几个方法来获取:

  • Constructor getConstructor(Class...):获取某个 public 的构造方法;
  • Constructor getDeclaredConstructor(Class...):获取某个构造方法;
  • Constructor[] getConstructors():获取所有 public 的构造方法;
  • Constructor[] getDeclaredConstructors():获取所有构造方法。

通常我们调用类的构造方法,这样写的(以 StringBuilder 为例):

// 实例化StringBuilder对象
StringBuilder name = new StringBuilder("Hello Mybj");

通过反射,要先获取 Constructor 对象,再调用 Class.newInstance() 方法:

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;public class ReflectionDemo {public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {// 获取构造方法Constructor constructor = StringBuffer.class.getConstructor(String.class);// 调用构造方法Object str = constructor.newInstance("Hello Mybj");System.out.println(str);}
}

运行结果:

Hello Mybj

5. 访问字段

前面我们知道了如何获取 Class 实例,只要获取了 Class 实例,就可以获取它的所有信息。

5.1 获取字段

Field 类代表某个类中的一个成员变量,并提供动态的访问权限。Class 提供了以下几个方法来获取字段:

  • Field getField(name):根据属性名获取某个 public 的字段(包含父类继承);
  • Field getDeclaredField(name):根据属性名获取当前类的某个字段(不包含父类继承);
  • Field[] getFields():获得所有的 public 字段(包含父类继承);
  • Field[] getDeclaredFields():获取当前类的所有字段(不包含父类继承)。

获取字段的实例如下:

package com.mybj.reflect;import java.lang.reflect.Field;public class MybjStudent1 {// 昵称 私有字段private String nickname;// 余额 私有字段private float balance;// 职位 公有字段public String position;public static void main(String[] args) throws NoSuchFieldException {// 类名.class 方式获取 Class 实例Class cls1 = MybjStudent1.class;// 获取 public 的字段 positionField position = cls1.getField("position");System.out.println(position);// 获取字段 balanceField balance = cls1.getDeclaredField("balance");System.out.println(balance);// 获取所有字段Field[] declaredFields = cls1.getDeclaredFields();for (Field field: declaredFields) {System.out.print("name=" + field.getName());System.out.println("\ttype=" + field.getType());}}}

运行结果:

public java.lang.String com.mybj.reflect.MybjStudent1.position
private float com.mybj.reflect.MybjStudent1.balance
name=nickname	type=class java.lang.String
name=balance	type=float
name=position	type=class java.lang.String

MybjStudent1 类中含有 3 个属性,其中 position 为公有属性,nickname 和 balance 为私有属性。我们通过类名.class 的方式获取了 Class 实例,通过调用其实例方法并打印其返回结果,验证了获取字段,获取单个字段方法,在没有找到该指定字段的情况下,会抛出一个 NoSuchFieldException

调用获取所有字段方法,返回的是一个 Field 类型的数组。可以调用 Field 类下的 getName() 方法来获取字段名称,getType() 方法来获取字段类型。

5.2 获取字段值

既然我们已经获取到了字段,那么就理所当然地可以获取字段的值。可以通过 Field 类下的 Object get(Object obj) 方法来获取指定字段的值,方法的参数 Object 为对象实例,实例如下:

package com.mybj.reflect;import java.lang.reflect.Field;public class MybjStudent2 {public MybjStudent2() {}public MybjStudent2(String nickname, String position) {this.nickname = nickname;this.position = position;}// 昵称 私有字段private String nickname;// 职位 公有属性public String position;public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {// 实例化一个 MybjStudent2 对象MybjStudent2 mybjStudent2 = new MybjStudent2("小码", "架构师");Class cls = mybjStudent2.getClass();Field position = cls.getField("position");Object o = position.get(mybjStudent2);System.out.println(o);}}

运行结果:

架构师

MybjStudent2 内部分别包含一个公有属性 position 和一个私有属性 nickname,我们首先实例化了一个 MybjStudent2 对象,并且获取了与其对应的 Class 对象,然后调用 getField() 方法获取了 position 字段,通过调用 Field 类下的实例方法 Object get(Object obj) 来获取了 position 字段的值。

这里值得注意的是,如果我们想要获取 nickname 字段的值会稍有不同,因为它是私有属性,我们看到 get() 方法会抛出 IllegalAccessException 异常,如果直接调用 get() 方法获取私有属性,就会抛出此异常。

想要获取私有属性,必须调用 Field.setAccessible(boolean flag) 方法来设置该字段的访问权限为 true,表示可以访问。在 main() 方法中,获取私有属性 nickname 的值的实例如下:

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {// 实例化一个 MybjStudent2 对象MybjStudent2 mybjStudent2 = new MybjStudent2("小码", "架构师");Class cls = mybjStudent2.getClass();Field nickname = cls.getDeclaredField("nickname");// 设置可以访问nickname.setAccessible(true);Object o = nickname.get(mybjStudent2);System.out.println(o);
}

此时,就不会抛出异常,运行结果:

小码

5.2 为字段赋值

为字段赋值也很简单,调用 Field.set(Object obj, Object value) 方法即可,第一个 Object 参数是指定的实例,第二个 Object 参数是待修改的值。我们直接来看实例:

package com.mybj.reflect;import java.lang.reflect.Field;public class MybjStudent3 {public MybjStudent3() {}public MybjStudent3(String nickname) {this.nickname = nickname;}// 昵称 私有字段private String nickname;public String getNickname() {return nickname;}public void setNickname(String nickname) {this.nickname = nickname;}public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {// 实例化一个 MybjStudent3 对象MybjStudent3 mybjStudent3 = new MybjStudent3("小码");Class cls = mybjStudent3.getClass();Field nickname = cls.getDeclaredField("nickname");nickname.setAccessible(true);// 设置字段值nickname.set(mybjStudent3, "Colorful");// 打印设置后的内容System.out.println(mybjStudent3.getNickname());}}

运行结果:

Colorful

6. 调用方法

Method 类代表某一个类中的一个成员方法。

6.1 获取方法

Class 提供了以下几个方法来获取方法:

  • Method getMethod(name, Class...):获取某个 public 的方法(包含父类继承);
  • Method getgetDeclaredMethod(name, Class...):获取当前类的某个方法(不包含父类);
  • Method[] getMethods():获取所有 public 的方法(包含父类继承);
  • Method[] getDeclareMethods():获取当前类的所有方法(不包含父类继承)。

获取方法和获取字段大同小异,只需调用以上 API 即可,这里不再赘述。

6.2 调用方法

获取方法的目的就是调用方法,调用方法也就是让方法执行。

通常情况下,我们是这样调用对象下的实例方法(以 String 类的 replace() 方法为例):

String name = new String("Colorful");
String result = name.replace("ful", "");

改写成通过反射方法调用:

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;public class ReflectionDemo1 {public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {// 实例化字符串对象String name = new String("Colorful");// 获取 method 对象Method method = String.class.getMethod("replace", CharSequence.class, CharSequence.class);// 调用 invoke() 执行方法String result = (String) method.invoke(name,  "ful", "");System.out.println(result);}
}

运行结果:

Color

代码中,调用 Method 实例的 invoke(Object obj, Object...args) 方法,就是通过反射来调用了该方法。

其中 invoke() 方法的第一个参数为对象实例,紧接着的可变参数就是要调用方法的参数,参数要保持一致。

7. 反射应用

Tips: 理解此部分内容可能需要阅读者有一定的开发经验

学习完了反射,大家可能依然非常疑惑,反射似乎离我们的实际开发非常遥远,实际情况也的确是这样的。因为我们在实际开发中基本不会用到反射。下面我来分享一个实际开发中应用反射的案例。

场景是这样的:有一个文件上传系统,文件上传系统有多种不同的方式(上传到服务器本地、上传到七牛云、阿里云 OSS 等),因此就有多个不同的文件上传实现类。系统希望通过配置文件来获取用户的配置,再去实例化对应的实现类。因此,我们一开始的思路可能是这样的(伪代码):

public class UploaderFactory {// 通过配置文件获取到的配置,可能为 local(上传到本地) qiniuyun(上传到七牛) private String uploader;// 创建实现类对象的方法public Uploader createUploader() {switch (uploader) {case "local":// 实例化上传到本地的实现类return new LocalUploader();case "qiniuyun":// 实例化上传到七牛云的实现类return new QiniuUploader();default:break;}return null;}
}

createUploader() 就是创建实现类的方法,它通过 switch case 结构来判断从配置文件中获取的 uploader 变量。

这看上去似乎没有什么问题,但试想,后续我们的实现类越来越多,就需要一直向下添加 case 语句,并且要约定配置文件中的字符串要和 case 匹配才行。这样的代码既不稳定也不健壮。

换一种思路考虑问题,我们可以通过反射机制来改写这里的代码。首先,约定配置文件的 uploader 配置项不再是字符串,改为类的全路径命名。因此,在 createUploader() 方法中不再需要 switch case 结构来判断,直接通过 Class.forName(uploader) 就可以获取 Class 实例,并调用其构造方法实例化对应的文件上传对象,伪代码如下:

public class UploaderFactory {// 通过配置文件获取到的配置,实现类的包名.类名private String uploader;// 创建实现类对象的方法public Uploader createUploader() {// 获取构造方法Constructor constructor = Class.forName(uploader).getConstructor();return (Uploader) constructor.newInstance();}
}

通过反射实例化对应的实现类,我们不需要再维护 UploaderFactory 下的代码,其实现类的命名、放置位置也不受约束,只需要在配置文件中指定类名全路径即可。

8. 小结

通过本小节的学习,我们知道了反射是 Java 提供的一种机制,它可以在程序的运行状态中,动态获取类的信息,注入类内部的属性和方法,完成对象的实例化等操作。获取 Class 对象有 3 种方法,通过学习反射的相关接口,我们了解到通过反射可以实现一切我们想要的操作。在本小节的最后,我也分享了一个我在实际开发中应用反射的案例,希望能对大家有所启发。

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

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

相关文章

机器视觉检测--光源

一,环形光源 较为常见的LED光源之一,提供基本的照明作用。 随着光源距离产品的工作距离LWD变化而产生的亮度分布,如下图暖色表示亮;冷色表示暗。 同时该图示是针对特定一款大小的环形光源的数据(下同)。 二…

酒店旅游API服务汇总

各大旅游平台常用API服务汇总: 实时房源服务【Airbnb】飞猪旅行开放服务途牛旅行开放平台API华为云数字差旅【差旅管理】动态信息接口【美团酒店】旅行商城商家管理API【马蜂窝】交易流程接口【美团酒店】电子导游【携程旅行】

【MachineLearning】| 机器学习:推动未来技术革新与应用的新引擎

一、引言 随着计算能力的飞速提升和大数据的广泛应用,机器学习已成为推动现代科技发展的关键力量。从自动化驾驶到精准医疗,再到金融风险评估,机器学习正逐步改变着我们的工作和生活方式。本文将围绕机器学习的技术革新及其在不同领域的应用…

TDMQ CKafka 版弹性存储能力重磅上线!

导语 自 2024年5月起,TDMQ CKafka 专业版支持弹性存储能力,这种产品形态下,存储可按需使用、按量付费,一方面降低消费即删除、存储使用波动大场景下的存储成本,另一方面存储空间理论上无穷大。 TDMQ CKafka 版产品能…

Python实用代码片段分享(三)

在今天的博文中,我们将继续分享一些Python编程中非常实用的代码片段。这些代码片段将帮助你更高效地处理常见任务,从字符转换到数据类型检查,应有尽有。 1. ord函数和chr函数 Python的ord()函数可以返回Unicode字符对应的ASCII码值&#xf…

数据结构——二叉树(C语言版)

前言 二叉树是一种非线性的数据结构。二叉搜索树、堆、红黑树等高阶数据结构都是依托于二叉树的基础实现的,所以我们有必要好好研究一下“二叉树”这种数据结构。本文只介绍二叉树的基础及中等用法,笔者能力有限,欠妥当之处欢迎批评指正。 树…

【RS】哨兵系列新网站无法下载的问题及解决办法(Sentinel-2)

最近有些小伙伴留言说哨兵数据无法下载,网站打开后会有一层蒙版,无法选取研究区等信息,今天就跟大家分享一下如何解决这个问题。还知道如何下载的小伙伴可以移步到之前的文章:【RS】欧空局Sentinel-2卫星数据下载(哨兵1、2、3、5P…

海外短剧看剧系统搭建部署,h5/app双端,系统页面一键翻译功能,批量上传素材等功能。

目录 前言: 一、海外短剧系统有是吗功能? 二、海外短剧项目在海外反馈怎么样? 总结: 前言: 海外短剧系统搭建开发,想进军海外短剧市场的,搭建这样一款海外短剧系统是必要的。海外短剧市场规…

ATA-4051C高压功率放大器应用分享:超声波测量液位系统

超声波测量液位是一种非接触式液位测量方法,其原理是利用超声波的传播特性来测量液位。超声波是一种高频机械波,其频率高于人类能够听到的频率,通常在100kHz以上。超声波具有较好的穿透性和反射性,可以在固体、液体和气体中传播&a…

FTP

文章目录 概述主动模式和被动模式的工作过程注意事项 概述 文件传输协议 FTP(File Transfer Protocol)在 TCP/IP 协议族中属于应用层协议,是文件传输标准。主要功能是向用户提供本地和远程主机之间的文件传输,尤其在进行版本升级…

ThinkBook 14 G6+ IMH(21LD)原厂Win11系统oem镜像下载

lenovo联想笔记本电脑原装出厂Windows11系统安装包, 恢复开箱状态自带预装系统,含恢复重置还原功能 链接:https://pan.baidu.com/s/1WIPNagHrC0wqYC3HIcua9A?pwdhzqg 提取码:hzqg 联想原装出厂系统自带所有驱动、出厂主题壁…

Zabbix安装:构建高效可靠的Zabbix监控系统

目录 引言 一、zabbix基本介绍 (一)什么是zabbix (二)zabbix结构体系 (三)zabbix监控对象 (四)zabbix进程 (五)zabbix监控模式 (六&#…

【SQL边干边学系列】01介绍性问题

文章目录 前言介绍性问题1.我们有哪些承运商?2. 从目录表中查询特定字段3.销售代表4.在美国的销售代表5.由特定员工ID下的订单6.供应商和联系人信息 答案1.我们有哪些承运商?2. 从目录表中查询特定字段3.销售代表4.在美国的销售代表5.由特定员工ID下的订…

Codes 重新定义 SaaS 模式的研发项目管理平台开源版 4.5.5 发布

一:简介 Codes 重新定义 SaaS 模式 云端认证 程序及数据本地安装 不限功能 30 人免费 Codes 是一个 高效、简洁、轻量的一站式研发项目管理平台。包含需求管理,任务管理,测试管理,缺陷管理,自动化测试&#xff0…

海外短剧的未来展望:创新与发展的方向

随着全球化的加速和互联网技术的飞速发展,海外短剧作为一种新兴的娱乐形式,正逐渐赢得广大观众的喜爱。在这个充满变革的时代,海外短剧面临着前所未有的机遇与挑战。本文将探讨海外短剧未来的创新与发展方向。 一、内容创新:打破传…

网络ip地址冲突会出现什么情况

在现代数字化社会中,网络IP地址扮演着至关重要的角色,它是设备在网络中唯一识别的标识。然而,当网络中出现IP地址冲突时,一系列问题便会随之而来。那么,网络ip地址冲突会出现什么情况呢?下面一起来跟虎观代…

k8s-部署对象存储minio

环境信息 minio版本 :最新 k8s 版本1.22 使用nfs作为共享存储 一.单节点安装包部署 脚本部署,一键部署,单节点应用于数据量小,一些缓存存储,比如gitlab-runner的产物数据,maven的打包依赖数据 #!/bin/bash# 步骤…

如何进行辐射抗扰度磁场测试?

一、为什么要进行闭环磁场测试? 辐射抗扰度测试中进行闭环磁场测试是为了评估设备在外部磁场影响下的性能表现。外部磁场可能来自各种源头,例如电力线、电动机、变压器等,这些磁场可能干扰设备的正常工作。闭环磁场测试通过模拟设备在实际工…

虾皮Lazada流量“滑铁卢”?自养号测评补单让你轻松翻盘!

当作为虾皮(Shopee)或Lazada平台的卖家时,密切关注流量数据是至关重要的。如果观察到店铺的流量出现下滑趋势,首要任务便是深入探究流量减少的根本原因。在明确了导致流量下滑的关键因素后,卖家便能更有针对性地采取措…

Java学习20——Map接口

目录 一.Map: 1.基本介绍: 2.Map常用方法: 3.Map的遍历方法: 4.HashMap: 1.基本介绍: 2.HashMap底层扩容机制: 5.Hashtable: 1.基本介绍: 2.HashMap和Hashtable的对比&…