Java,反射机制与反射的应用

关于反射:

有时对象的编译时类型和运行时类型是不一致的。比如在使用多态的场景下,有一个Object类型的数组,其中的元素有着各种不同的类型,而调用相应的元素的方法时,比如调用toString方法时,希望调用的是各个元素相对应的类型的toString方法。如果用instanceof来创建if判断再进行相应的强转再进行调用,当类型太多时,就太繁琐,也易出错。这时便要用到反射机制。

反射机制:

反射(Reflection)是被视为动态语言的关键,反射机制使得程序在运行期间借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性和方法。

加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象,一个类只有一个Class对象,这个Class对象包含了完整的类的结构信息。可以通过这个Class对象看到类的结构。

关于Class类的理解:

针对于编写好的.java源文件进行编译(使用javac.exe指令),会生成一个或多个.class字节码文件。接着,我们使用java.exe指令对指定的.class文件进行解释运行。这个解释运行的过程中,我们需要将.class字节码文件加载(使用类的加载器加载到内存中的方法区)到内存中。加载到内存中的结构即为Class的一个实例。也称为运行时类。只要类型和维度一致,就是同一个Class的的实例。

Class看作是反射的源头。

获取Class实例的方式:(若A、B、C、D分别是某个类)

方式①:调用运行时类的静态属性 class

Class classA = A.class;

方式②:调用运行时类的对象的getClass( )方法

(若bb是B类的实例)

Class classB = bb.getClass( );

方式③:调用Class的静态方法forName(String className)

Class classC = Class.forName(C的全类名);  (全类名是指定了位于哪个包下的信息的类名)

方式④:使用类的加载器的方式

Class classD = ClassLoader.getSystemClassLoader( ).loadClass(D的全类名);

类的加载过程:

过程①:类的装载(Loading)

将类的class文件读入内存,并为其创建一个java.lang.Class对象,此过程具体有由类的加载器完成。

过程②:链接(Linking)

  1. 验证(Verify):确保加载的类的信息符合jvm规范。比如,以cafebabe开头,无安全方面问题。

  2. 准备(Prepare):正式为类变量(静态变量)分配内存并设置类变量默认初始化值的阶段,这些内存都将在方法区中进行分配。

  3. 解析(Resolve):虚拟机中常量池内的符号引用(常量名)替换为直接引用(地址)的过程。

过程③:初始化(Initialization)

  • 执行类构造器<clinit>( )方法的过程。

类构造器<clinit>( )方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(即完成静态变量的显示赋值和静态代码块中赋值的操作)

  • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。

类的加载器(以jdk8)为例:

作用:负责类的加载,并对应于一个Class的实例。

分类(分为两种):

①BootstrapClassLoader:引导类加载器、启动类加载器。

        使用c/c++的代码写的,不能通过Java代码获取其实例。

        负责加载Java的核心库。

②继承于ClassLoader的类加载器:

  • ExtensionClassLoader:扩展类加载器。

  • SystemClassLoader/ApplicationClassLoader:系统类加载器、应用程序类加载器。(自定义的类默认使用的类的加载器)

  • 用户自定义类的加载器:实现应用的隔离(使得同一个类在一个应用程序中可以加载多份),数据的加密。

关于反射的应用:

应用①:创建运行时类的对象

创建运行时类的对象的操作:

通过Class的实例调用newInstance( )方法即可

创建运行时类的要求:

①运行时类中必须提供一个空参构造器。

②要求符合提供的空参的构造器的权限

JavaBean中要求类中要有一个空参构造器。一是因为子类对象在实例化时,子类的构造器默认调用父类的空参构造器,防止调用出错(也可以指明调用父类的有参构造器)。二是在反射中,创建运行时类的对象时,各个运行时类都提供一个空参构造器,便于编写通用的创建运行时类的代码。

newInstance( )在jdk9中标识为过时的,替换为通过Constructor类调用newInstance(可变形参)。

应用②:获取运行时类的内部结构

获取的结构:所有的属性、方法、构造器。父类、接口,包、带泛型的父类、父类的泛型 等。

注:获取父类的泛型的方式的代码为:

Class c1 = Class.forName("反射.类的加载器.Person");
//获取带泛型的父类(Type是一个接口,Class类实现了此接口)
Type superclass = c1.getGenericSuperclass();
//如果父类是带泛型的,则可以强转为ParameterizedType类型的
ParameterizedType parameterizedType = (ParameterizedType) superclass;
//调用getActualTypeArguments()获取泛型参数,结果是一个数组,因为可能有多个泛型参数。
Type[] arguments = parameterizedType.getActualTypeArguments();
//获取泛型参数的名称
for (int i = 0; i < arguments.length; i++)
{System.out.println(((Class)arguments[i]).getName());
}

应用③:调用指定的属性、方法、构造器

一、调用属性
调用指定属性的步骤:
  • 调用public的属性(非静态):

①    通过类的Class的实例(即运行时类)调用getField(String name)方法,传入要调用的属性名。返回值即为运行时类的属性的Field实例。

②    获取属性:再用Field实例调用其公有的属性的get(Object obj)方法,参数传入相应类的实例对象,返回值即为指定的属性。/操作属性:调用Field实例的set(Object obj,Object value),参数一传入要修改的此类的对象,参数二传入要对此属性修改的内容value,即可将对象的相应属性修改为value的值。

  • 调用其他的不符合其访问权限的属性(非静态):

①    通过类的Class的实例(即运行时类)调用getDeclaredField(String name)方法,传入要调用的属性名,返回值即为其运行时类的属性的Field实例(只要在相应类中声明过的属性都可以获取)。

②    用Field实例调用其setAccessiable(boolean flag)方法,传入true,即可以表示在权限外访问此属性。如果不调用其setAccessiable(boolean flag)方法,则默认为传入false,即不允许在权限外调用此属性。

③    获取属性:再用Field实例调用其公有的属性的get(Object obj)方法,参数传入相应类的实例对象,返回值即为指定的属性。/操作属性:调用Field实例的set(Object obj,Object value),参数一传入要修改的相应类的实例对象,参数二传入要对此属性修改的内容value,即可将对象的相应属性修改为value的值。

操作代码的例子如下(此处异常已抛出到方法声明处,需处理异常):

注:Person类的age属性是用public权限修饰,String属性是用private权限修饰。

//创建类Person的Class实例
Class cc = Person.class;
//通过运行时类调用newInstance()方法创建对象
Person pp = (Person) cc.newInstance();
//获取运行时类的名为age的属性的Field实例
Field ageField = cc.getField("age");
System.out.println(ageField.get(pp));
//将pp的age属性赋值为2
ageField.set(pp,2);
System.out.println(ageField.get(pp));
System.out.println();
//获取运行时类的名为name的属性的Field实例
Field nameField = cc.getDeclaredField("name");
//表明可以在权限外访问name属性
nameField.setAccessible(true);
//将pp对象的name属性赋值为Tom
nameField.set(pp,"Tom");
System.out.println(nameField.get(pp));
  • 调用静态的属性

(以private权限修饰的静态属性为例)

①    通过类的Class的实例(即运行时类)调用getDeclaredField(String name)方法,传入要调用的属性名,返回值即为其运行时类的属性的Field实例(只要在相应类中声明过的属性都可以获取)。

②    用Field实例调用其setAccessiable(boolean flag)方法,传入true,即可以表示在权限外访问此属性。如果不调用其setAccessiable(boolean flag)方法,则默认为传入false,即不允许在权限外调用此属性。(①和②步骤与前面的调用其他的不符合其访问权限的属性(非静态)的步骤一致)

③    获取属性:再调用Field实例的get(Object obj)方法,参数传入相应类的Class的实例,返回值即为指定的属性。/操作属性:调用Field实例的set(Object obj,Object value),参数一传入要修改的相应类的Class实例,参数二传入要对此属性修改的内容value,即可将此类的相应属性修改为value的值。

注:与调用其他的不符合其访问权限的属性(非静态)的步骤不同的是,由于静态属性(类变量)与类有关,并非与类的对象有关,所以调用时的获取和操作时的get和set方法中传入的参数应该为类的Class实例(运行时类),并非类的对象。

操作代码的例子如下(此处异常已抛出到方法声明处,需处理异常):

注:Person的静态属性info的声明为:private static String info;

//创建类Person的Class实例
Class cc = Person.class;
//获取info的属性的Field的实例
Field infoField = cc.getDeclaredField("info");
//表明可以在权限外访问info属性
infoField.setAccessible(true);
//在set和get方法中的参数是Person.class或cc
System.out.println(infoField.get(cc));
infoField.set(cc,"world");
System.out.println(infoField.get(cc));

二、调用方法

调用指定方法的步骤:

  • 调用public的方法(非静态):

①    通过类的Class的实例(即运行时类)调用getMethod(String name,Class<?> ... parameterType)方法,先传入要调用的类的方法名,再传入该方法的参数的参数类型的Class实例(如:int.class,String.class)。获取运行时类的公有(public)的方法的Method实例。

②    调用方法:调用Method实例的invoke(Object obj,Object ... args)方法,先传入要调用方法的对象,再传入要调用的方法的实参参数值。invoke方法的返回值即为目标方法的返回值。

  • 调用其他的不符合其访问权限的方法(非静态):

①    通过类的Class的实例(即运行时类)调用getDeclaredMethod(String name,Class<?> ... parameterType)方法,先传入要调用的类的方法名,再传入该方法的参数的参数类型的Class实例(如:int.class,String.class)。获取运行时类的方法的Method实例。

②    用Method实例调用其setAccessiable(boolean flag)方法,传入true,即可以表示在权限外访问此方法。如果不调用其setAccessiable(boolean flag)方法,则默认为传入false,即不允许在权限外调用此方法。

③    调用方法:调用Method实例的的invoke(Object obj,Object ... args)方法,先传入要调用方法的对象,再传入要调用的方法的实参参数值。invoke方法的返回值即为Method实例对应的方法的返回值。如果Method实例对应的方法的返回值为void,则invoke方法的返回值为null。

  • 调用静态的方法:

(以private权限修饰的静态方法为例)

与调用其他的不符合其访问权限的方法(非静态)不同的是,在③步骤中,调用Method实例的的invoke(Object obj,Object ... args)方法,先传入的是要调用方法的类的Class实例,再传入要调用的方法的实参参数值。

反射调用方法的操作代码的例子如下(此处异常已抛出到方法声明处,需处理异常):

//创建类Person的Class实例
Class cc = Person.class;
//通过运行时类调用newInstance()方法创建对象
Person pp = (Person) cc.newInstance();
//获取运行时类的名为age的属性的Field实例
Field ageField = cc.getField("age");
System.out.println(ageField.get(pp));
//将pp的age属性赋值为2
ageField.set(pp,2);
System.out.println(ageField.get(pp));
System.out.println();
//获取运行时类的名为name的属性的Field实例
Field nameField = cc.getDeclaredField("name");
//表明可以在权限外访问name属性
nameField.setAccessible(true);
//将pp对象的name属性赋值为Tom
nameField.set(pp,"Tom");
System.out.println(nameField.get(pp));

三、调用构造器

(以private权限修饰的构造器为例)

①    通过类的Class的实例(即运行时类)调用getDeclaredConstructor(Class<?> ... parameterType)方法,传入该构造器的参数的参数类型的Class实例(如:int.class,String.class)。获取运行时类的构造器的Constructor实例。

②    用Constructor实例调用其setAccessiable(boolean flag)方法,传入true,即可以表示在权限外访问此构造器。如果不调用其setAccessiable(boolean flag)方法,则默认为传入false,即不允许在权限外调用此构造器。

③调用Constructor实例的newInstance(Object ... initargs)方法,参数传入构造器中的参数的实参参数值,返回值即为此类的对象(Object类型)。

注:可以用Constructor实例调用newInstance的方式创建对象来替换原来的用Class实例调用newInstance的方式。

调用构造器操作代码如下:

//创建Person的Class实例
Class cc = Person.class;
//调用getDeclaredConstructor的方法获取相应构造器的Constructor实例
Constructor ccDeclaredConstructor = cc.getDeclaredConstructor(int.class, String.class);
//表明可以在权限外使用此构造器
ccDeclaredConstructor.setAccessible(true);
//调用Constructor实例的newInstance方法创建对象。
Person pp = (Person) ccDeclaredConstructor.newInstance(19, "liergou");
System.out.println(pp);

应用④:获取指定的注解

获取类声明上的注解:

用类的Class实例调用getDeclaredAnnotation(Class annotation)方法,参数传入注解的Class实例,返回值即为相应的注解(Annotation类型的,要进行具体操作,需要强转为具体的注解类型)。

获取属性声明上的注解:

先调用getDeclaredField方法获取类的属性的Field实例,调用getDeclaredAnnotation(Class annotation)方法,参数传入注解的Class实例,返回值即为相应的注解(Annotation类型的,要进行具体操作,需要强转为具体的注解类型)。

获取方法和构造器上的注解同理。

反射的优缺点:

反射的优点是提高了程序的灵活性和拓展性,降低了耦合性,提高自适应能力,允许程序创建和控制任何类的对象,无需提前硬编码目标类。

反射的缺点是放射的性能较低,会模糊程序内部逻辑结构,可读性较差。反射机制主要应用在对灵活性和拓展性要求很高的系统框架上。

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

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

相关文章

优化机器学习:解析数据归一化的重要性与应用

在机器学习中&#xff0c;数据归一化是一种数据预处理的技术&#xff0c;旨在将数据转换为相似的范围或标准化的分布。这样做的主要目的是消除不同特征之间的量纲差异或数值范围差异&#xff0c;以确保模型在训练时更稳定、更有效地学习特征之间的关系。 通常&#xff0c;机器…

linux下ffmpeg安装

1.下载安装MP3编码库 因为FFmpeg默认只支持mp3的解码&#xff0c;不支持mp3编码。如果想把提取出来的音频保存为mp3格式肯定就需要mp3格式的编码库。因此&#xff0c;想输出mp3文件&#xff0c;需要借助第三方的mp3编码库。这里采用LAME编码库&#xff0c;即Lame Aint an MP3 E…

智能优化算法应用:基于水循环算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于水循环算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于水循环算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.水循环算法4.实验参数设定5.算法结果6.参考文献7.…

Oracle把多行查询结果合并为一行

SELECT zjxm FROM pb_zjzcy&#xff0c;Oracle数据库&#xff0c;把zjxm合并到一个字段 在Oracle数据库中&#xff0c;你可以使用LISTAGG函数将多个行中的zjxm字段合并到一个字段中。以下是一个示例查询&#xff1a; SELECT LISTAGG(zjxm, ,) WITHIN GROUP (ORDER BY zjxm) A…

安卓apk抓包

起因 手机&#xff08;模拟器&#xff09;有时候抓不到apk的包&#xff0c;需要借助Postern设置一个代理&#xff0c;把模拟器的流量代理到物理机的burp上。 解决方案 使用Postern代理&#xff0c;把apk的流量代理到burp。 Postern是一个用于代理和网络流量路由的工具&#xf…

C++ day44完全背包问题 零钱兑换Ⅱ 组合总和Ⅳ

完全背包&#xff1a;一个物品可以使用无数次&#xff0c;将01背包中倒序遍历背包变成正序遍历背包 遍历顺序&#xff1a;在完全背包中&#xff0c;对于一维dp数组来说&#xff0c;其实两个for循环嵌套顺序是无所谓的&#xff01; 先遍历物品&#xff0c;后遍历背包可以&#…

win10 下 mvn install 报错:编码GBK不可映射字符

问题背景 由于jenkins需要部署不同的项目&#xff0c;需要使用不同的jdk版本&#xff0c;所以需要配置单独的settings.xml&#xff0c;使用指定的jdk版本进行编译&#xff0c;这里需要单独的maven设置&#xff0c;在配置完后进行mvn的install的时候&#xff0c;由于存在中文注释…

Maven——Maven使用基础

1、安装目录分析 1.1、环境变量MAVEN_HOME 环境变量指向Maven的安装目录&#xff0c;如下图所示&#xff1a; 下面看一下该目录的结构和内容&#xff1a; bin&#xff1a;该目录包含了mvn运行的脚本&#xff0c;这些脚本用来配置Java命令&#xff0c;准备好classpath和相关…

在 Nginx 配置中,root 和 alias 指令的区别是什么

疑问root和alias的区别是什么, 如下 location / {alias /Users/lixinyu/MySpace/学成在线-plus/xc-ui-pc-static-portal/;index index.html index.htm;}location / {root /Users/lixinyu/MySpace/学成在线-plus/xc-ui-pc-static-portal/;index index.html index.htm;}解释…

Cytoscape软件下载、安装、插件学习[基础教程]

写在前面 今天分享的内容是自己遇到问题后&#xff0c;咨询社群里面的同学&#xff0c;帮忙解决的总结。 关于Cytoscape&#xff0c;对于做组学或生物信息学的同学基本是陌生的&#xff0c;可能有的同学用这个软件作图是非常溜的&#xff0c;做出来的网络图也是十分的好看&am…

【海思SS528 | VDEC】MPP媒体处理软件V5.0 | VDEC的使用总结

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

距离“全自动”漏洞挖掘又近了一步!腾讯安全大数据实验室论文入选ACM CCS 2023

计算机领域国际权威学术顶会ACM CCS 2023于11月26日在丹麦哥本哈根开幕。腾讯安全大数据实验室团队论文《Hopper: Interpretative Fuzzing for Libraries》被大会收录&#xff0c;昨天&#xff0c;实验室研究员谢雨轩受邀出席大会进行主题分享。 该论文提出了解释性模糊测试&a…

构建私有Registry

当镜像的数量越来越多&#xff0c;镜像管理就变得非常有必要了。有时候我们需要设置一个本地的私人仓库来进行镜像的管理。 本关任务是学习创建和操作一个私人仓库&#xff0c;要求学习者参照示例完成“创建一个私人仓库&#xff0c;将busybox镜像推送到私人仓库&#xff0c;最…

UCSC基因组浏览器用法

UCSC基因组浏览器用法 UCSC基因组浏览器是一个强大的在线工具&#xff0c;主要用于查看和分析多种生物的基因组数据。这个浏览器最初是由加利福尼亚大学圣克鲁兹分校的生物信息学家和计算生物学家开发的&#xff0c;旨在为科研人员提供一个易于访问和使用的界面&#xff0c;用于…

Java数据结构之《合并线性表》问题

一、前言&#xff1a; 这是怀化学院的&#xff1a;Java数据结构中的一道难度中等偏下的一道编程题(此方法为博主自己研究&#xff0c;问题基本解决&#xff0c;若有bug欢迎下方评论提出意见&#xff0c;我会第一时间改进代码&#xff0c;谢谢&#xff01;) 后面其他编程题只要我…

DELETE 请求,如何通过ajax进行发送

基本的 DELETE 请求概念 DELETE 请求用于向服务器发送删除资源的请求。它是 RESTful API 中的一个重要方法&#xff0c;用于删除指定的资源。 在 Axios 中&#xff0c;发送 DELETE 请求需要指定目标 URL&#xff0c;并可选地传递一些参数&#xff0c;例如请求头、请求体等。DE…

spring和springBoot

Spring和Spring Boot小结 Spring和Spring Boot基于IOC AOP理念实现&#xff0c;Spring Boot集成了Spring。Spring框架&#xff1a; Spring框架解决了企业级的开发的复杂性&#xff0c;它是一个容器框架&#xff0c;用于装java对象&#xff08;Bean&#xff09;&#xff0c;使程…

freertos任务调度机制深度分析(以RISC-V架构为例)

1、前言 本文是以RISC-V架构为例进行讲解&#xff0c;在汇编代码层面和ARM架构不一样&#xff0c;但是整体框架是一样的侧重任务调度底层机制讲解&#xff0c;讲解代码只保留了基本功能&#xff0c;可配置的功能基本都已经删除本文是以可抢占式调度机制进行讲解RISC-V架构只支持…

【ZEDSLAM】Ubuntu18.04系统ZED 2i双目相机SDK安装、联合标定、SLAM测试

0.设备、环境和说明 笔记本电脑i5-8300H、GTX 1060、32GRAM 因为后面要测试Vins-Fusion和ORB-SLAM3&#xff0c;所以推荐安装Ubuntu 18.04&#xff08;或者Ubuntu 20.04&#xff09; ROS 1&#xff08;不建议用比Ubuntu18更低的版本&#xff09; ROS一键安装命令&#xff1a;…

智能监控平台/视频共享融合系统EasyCVR接入RTSP协议视频流无法播放原因是什么?

视频集中存储/云存储/视频监控管理平台EasyCVR能在复杂的网络环境中&#xff0c;将分散的各类视频资源进行统一汇聚、整合、集中管理&#xff0c;实现视频资源的鉴权管理、按需调阅、全网分发、智能分析等。AI智能/大数据视频分析EasyCVR平台已经广泛应用在工地、工厂、园区、楼…