Reflection反射

news2024/11/19 1:48:52

 概述      

        Java反射机制是java语言的一个重要特性,首先我们要了解两个概念:编译期和运行期。

编译期

        编译期是指把源代码交给编译器编译成计算机可以执行的文件的过程。在Java中,也就是把Java代码编译成class文件的过程,编译器只是做了一些翻译功能,并没有把代码放在内存中运行起来。

运行期

        是指把编译后的文件交给计算机执行,直到程序结束,所谓运行期就是把在磁盘中的代码放在内存中运行起来。

        而Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;

对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

        Java反射机制主要提供了以下功能,这些功能都基于java.lang.reflect包:

①在运行时判断任意一个对象所属的类

②在运行时构造任意一个类的对象

③在运行时判断任意一个类所具有的成员变量和方法

④在运行时调用任意一个对象的方法

⑤生成动态代理

class类

        想要知道一个类的属性和方法,必须先获取到该类的字节码文件(.class)对象。class的本质是数据类型,class是由JVM在执行过程中动态加载的。JVM在第一次读取到一种class类型时,就会将其加载进内存。每加载一中class,JVM就为其创建一个class类型的实例,并关联起来:

注意:这里的class类型是一个名叫Classde类:

//final声明不允许继承
public final class Class{
    //私有的构造方法
    private Class(){}
}

        由于JVM为每个加载class创建了对应的class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、成员变量等,因此,如果获取了某个class实例,我们就可以通过class实例获取到该实例对应的class信息。这种通过class实例获取class信息的方法称为反射(reflect)。

        那么如何获取一个class的class实例呢?有三种:

方法一:直接通过一个class的静态变量class获取:

Class cls=String.class;

方法二:如果有一个实例变量,可以通过getClass()方法获取:

String s="Hello";
Class cls=s.getClass();

方法三:如果知道一个class的完整类名,可以通过静态方法class.forName()获取:

Class cls=Class.forName("java.lang.String");

        但是由于class实例在JVM中是唯一的,所以,上述三个方法获取的class实例是同一个实例:

public class Test {
	public static void main(String[] args) throws ClassNotFoundException {
		//方式一:通过类名
		Class stringClass1=String.class;
		
		//方式二:通过Class类的forName()方法
		Class stringClass2=Class.forName("java.lang.String");
		
		//方式三:通过对象调用getClass()方法
		Class stringClass3="".getClass();
		System.out.println(stringClass1.hashCode());
		System.out.println(stringClass2.hashCode());
		System.out.println(stringClass3.hashCode());
	}

}

输出结果:

        如果获取到了一个class实例,我们就可以通过该class实例来创建对应类型的实例,可以用来代替:Order o1=new Order():

//用反射的方式创建对象
String className="com.ape.Order";
//获取类信息
Class classInfo=Class.forName(className);
//创建对象
Order o2=(Order)classInfo.newInstance();

        虽然通过newInstance()可以创建实例,但是它的局限是:只能调用public修饰的无参构造方法。值得注意的是,采用硬编码的方式创建对象(Order o1=new Order();)是在编译期进行的;而用反射的方式创建对象是在运行期进行的。

Class常用方法

getPackage()

返回值为Package对象,用来获取该类的存放路径(包路径):

Class clz=Class.forName("java.util.HashMap");
System.out.println("package包名:"+clz.getPackage());

运行结果:package java.util, Java Platform API Specification, version 1.8

getName()

返回值为String对象,用来获取该类的名称:

①getName():获取完全限定名

②getSimpleName():简单类名

Class clz=Class.forName("java.util.HashMap");
System.out.println("完全限定名:"+clz.getName());
System.out.println("简单类名:"+clz.getSimpleName());

运行结果:

完全限定名:java.util.HashMap
简单类名:HashMap

getSuperclass()

返回值为class对象,获取该类继承的类:

Class clz=Class.forName("java.util.HashMap");
System.out.println("父类:"+clz.getSuperclass());

运行结果:父类:class java.util.AbstractMap

getInterface()

返回值为class型数组,获取该类实现的所有接口:

System.out.println("实现的接口:"+clz.getInterfaces());

结果:实现的接口:[Ljava.lang.Class;@15db9742

getMethods()

1、getMethods()

返回值为Methods数组,获取public修饰的成员方法:

Method[] mthodArray=clz.getMethods();
	System.out.println("成员方法:");
	for(Method m:mthodArray) {
		System.out.println(m.getName());
	}

运行结果:

成员方法:
remove
remove
get
put
values
clone
clear
isEmpty
replace
replace
replaceAll
size
entrySet
putAll
putIfAbsent
forEach
keySet
compute
computeIfAbsent
computeIfPresent
containsKey
containsValue
getOrDefault
merge
equals
toString
hashCode
wait
wait
wait
getClass
notify
notifyAll

 2、getDeclaredMethods()

返回值为Methos对象,获取当前对象的所有方法:

Method[] mthodArray=clz.getDeclaredMethods();
	System.out.println("成员方法:");
	for(Method m:mthodArray) {
		System.out.println(m.getName());
	}

运行结果:

成员方法:
remove
remove
get
put
hash
values
clone
clear
isEmpty
replace
replace
replaceAll
size
entrySet
putAll
putIfAbsent
readObject
writeObject
forEach
keySet
compute
computeIfAbsent
computeIfPresent
containsKey
containsValue
getOrDefault
loadFactor
merge
capacity
afterNodeAccess
afterNodeInsertion
afterNodeRemoval
comparableClassFor
compareComparables
getNode
internalWriteEntries
newNode
newTreeNode
putMapEntries
putVal
reinitialize
removeNode
replacementNode
replacementTreeNode
resize
tableSizeFor
treeifyBin

getConstructors()

1、getConstructors()

返回值为Constructors型数组,获取public修饰的构造方法:

Constructor[] constructorArray1=clz.getConstructors();
2、getDeclaredConstructors()

返回值为Constructors对象,获取所有构造方法:

Constructor[] constructorArray2=clz.getDeclaredConstructors();

getFields()

1、getFields()
Field[] fileArray=clz.getFields();
	System.out.println("成员变量(字段):");
	for(Field f:fileArray) {
		System.out.println(f.getName());
	}
1、getDeclaredFields()
Field[] fileArray=clz.getDeclaredFields();
	System.out.println("成员变量(字段):");
	for(Field f:fileArray) {
		System.out.println(f.getName());
	}

运行结果:

成员变量(字段):
serialVersionUID
DEFAULT_INITIAL_CAPACITY
MAXIMUM_CAPACITY
DEFAULT_LOAD_FACTOR
TREEIFY_THRESHOLD
UNTREEIFY_THRESHOLD
MIN_TREEIFY_CAPACITY
table
entrySet
size
modCount
threshold
loadFactor

动态加载机制

        JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次用到class时才加载

调用构造方法

        为了可以调用任意的构造方法,Java的反射API提供了Constructor对象,它包含了一个构造方法的所有信息,可以创建一个实例。Class类为我们提供了以下几个方式来获取构造方法:

①Constructor[ ]  getConstructors():获取所有public修饰的构造器

②Constructor[ ] getDeclaredConstructors():获取所有构造器

③Constructor  getConstructor(name):获取Public修饰的指定构造器

④Constructor  getDeclaredConstructor(name):获取任意指定构造器

无参构造方法

//首先准备一个Document类:

package date_0909;

public class Document {
	private int size;
	public String fileName;
	
	//静态方法
	public static void dosth() {
		System.out.println("执行静态方法");
	}
	
	//构造方法
	public Document() {
		super();
	}

	public Document(int size) {
		super();
		this.size = size;
	}

	public Document(int size, String fileName) {
		super();
		this.size = size;
		this.fileName = fileName;
	}

	public Document(String fileName) {
		super();
		this.fileName = fileName;
	}

	public int getSize() {
		return size;
	}

	public void setSize(int size) {
		this.size = size;
	}

	public String getFileName() {
		return fileName;
	}

	public void setFileName(String fileName) {
		this.fileName = fileName;
	}

	@Override
	public String toString() {
		return "Document [size=" + size + ", fileName=" + fileName + "]";
	}
	
	

}
//获取无参构造方法
Constructor constructor1=clz.getDeclaredConstructor();
//执行构造器(构造方法),创建对象
Object obj1=constructor1.newInstance();
System.out.println(obj1);

运行结果:Document [size=0, fileName=null]

有参构造方法

案例1:

//获取有参构造方法
Constructor constructor2=clz.getDeclaredConstructor(int.class);
//执行有参构造方法
Object obj2=constructor2.newInstance(123);
System.out.println(obj2);

运行结果:Document [size=123, fileName=null]

案例2:

Constructor constructor3=clz.getDeclaredConstructor(int.class,String.class);
Object obj3=constructor3.newInstance(233,"陇西");
System.out.println(obj3);

运行结果:Document [size=233, fileName=陇西]

getDeclaredConstructors()和getConstructors()

        我们将Document类中的两个构造方法改为私有:

//构造方法
public Document() {
	super();
}
private Document(int size) {
	super();
	this.size = size;
}
public Document(int size, String fileName) {
	super();
	this.size = size;
	this.fileName = fileName;
}
private Document(String fileName) {
	super();
	this.fileName = fileName;
}

        当我们使用getConstructors()来获取构造方法时,只能获取到public 修饰的构造方法,那么当我们想通过getConstructors()获取private修饰的构造方法时,会产生报错:

Class clz=Class.forName("date_0909.Document");
//获取一组构造器
Constructor[] constructorArray1=clz.getConstructors();

//获取指定构造器
Constructor constructor2=clz.getConstructor(String.class);
System.out.println(constructor2);

        但是 getDeclaredConstructor()可以获取到任意构造方法,并且可以通过调用构造器来创建对象:

//获取指定构造器
Constructor constructor=clz.getDeclaredConstructor(String.class);
System.out.println(constructor);
//调用私有构造方法,必须设置他的访问权限为true
constructor.setAccessible(true);
//调用构造器,创建对象
Object obj=constructor.newInstance("小团子");
System.out.println(obj);

运行结果:

private date_0909.Document(java.lang.String)

Document [size=0, fileName=小团子]

访问成员变量

        对于任意一个实例,只要我们获取了它的Class,就可以获取它的一切信息。比如通过一个Class实例获取字段信息,class类提供了以下几个方法来获取成员变量:

①Field getField(name):根据字段名获取当前类中某个public修饰的成员变量。

②Field  getDeclaredField(name):根据字段名获取当前类中定义的某个成员变量。

③Field[ ] getFields():获取所有public修饰的成员变量。

④Field[ ] getDeclaredFields():获取所有成员变量。

        我们通过反射,访问并使用成员变量,可用来代替传统的硬编码的形式,用来操作成员变量:

Document doc1=new Document();
doc1.fileName("wyj");
doc1.size(165);

通过反射的方式:

Class clz=Class.forName("date_0909.Document");
//创建对象
Object obj=clz.newInstance():

//获取指定名称的成员变量
Field fileName2=clz.getDeclaredField("fileName");
Field size2=clz.getDeclaredField("size");

//访问私有的成员变量时需要设置访问权
size2.setAccessible(true);

//使用成员变量,存入数据
fileName2.set(obj,"wyj");
size2.set(obj, 165);
System.out.println(obj);

运行结果:Document [size=165, fileName=wyj]

        那么,如果我们可以使用反射来获取private字段的值,那我们类的封装有什么意义?正常情况下,我们都会使用doc.fielNAme来访问Document的fileName字段,编译器会根据public、private、protected决定是否允许访问该字段,这就是封装的目的。而反射是一种非常规的用法,它会破坏对对象的封装。它的代码复杂,它更多的是给工具或底层架构来使用,目的是在不知道目标实例的任何信息情况下,获取特定字段。

调用方法

        我们可以通过class实例获取所有方法(Method类型的对象),Class提供了以下几个方法来获取Method:

①Method getMethod(name):根据字段名获取当前类中某个public修饰的成员方法。

②Method   getDeclaredMethod(name):根据字段名获取当前类中定义的某个成员方法。

③Method [ ] getMethods():获取所有public修饰的成员方法。

④Method [ ] getDeclaredMethod s():获取所有成员方法

我们通过反射,访问并使用成员方法,可用来代替传统的硬编码的形式,用来操作成员方法:

Document doc1=new Document();
doc1.setFileName("海底两万里");
doc1.setSize(666);

通过反射的方法:

Class clz=Class.forName("date_0909.Document");
//创建对象
Object obj1=clz.newInstance();

//获取指定方法和参数
Method setNameMethod=clz.getMethod("setFileName",String.class);
Method setSizeMethod=clz.getMethod("setSize", int.class);

//执行方法
setNameMethod.invoke(obj1, "海底两万里");
setSizeMethod.invoke(obj1, 10034);
System.out.println(obj1);

运行结果:Document [size=10034, fileName=海底两万里]

调用静态方法

//案例一:

//硬编码的方式

Document.dosth();

//反射的方式

Class clz=Class.forName("date_0909.Document");
Method dosthMethod=clz.getMethod("dosth");
dosthMethod.invoke(null);

//案例二:

//硬编码的方式

String ret1=String.format("HashMap的默认初始容量是%d,加载因子是%f", 16,.75f);

//反射的方式

Class clz=String.class;
Method formatMethod=clz.getMethod("format", String.class,Object[].class);
String ret2=formatMethod.invoke(null,"HashMap的默认初始容量是%d,加载因子是%f", new Object[] {16,0.75f}).toString();
System.out.println(ret2);

运行结果:HashMap的默认初始容量是16,加载因子是0.750000

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

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

相关文章

使用servlet将图片保存到数据库中

一、导入jar包,配置properties文件(src路径下) commons-fileupload-1.3.1.jar commons-io-2.2.jar druid-1.2.8.jar mysql-connector-java-8.0.25.jar servlet-api.jar # ????key-value??? driverClassNamecom.mysql.cj.jdbc.Driv…

优购电商小程序的设计与实现+ssm(lw+演示+源码+运行)

优购电商小程序 摘 要 随着社会的发展,社会的方方面面都在利用信息化时代的优势。互联网的优势和普及使得各种系统的开发成为必需。 本文以实际运用为开发背景,运用软件工程原理和开发方法,它主要是采用java语言技术和mysql数据库来完成对…

精选推荐!分享6款论文写作ai免费带附加文献

在当今学术研究和写作领域,AI论文写作工具已经成为不可或缺的助手。这些工具不仅能够提高写作效率,还能帮助研究人员和学生产出高质量的论文。以下是六款免费且功能强大的AI论文写作工具推荐: 一、千笔-AIPassPaper 千笔-AIPassPaper是一款…

【试听开放中】新中地2406期GIS特训营同步,定制专属学习路线图+职业规划

很多地信相关专业的同学都知道 学习开发很重要?项目实战经验很重要 但是在面对学什么语言?掌握什么技术的时候 同学又就开始犯嘀咕,这么多,到底学什么? 今天听人说Java不错,想学 明天听人说Python前景…

掌握“问一问”策略,视频号流量轻松实现质的飞跃!

掌握“问一问”策略,视频号流量轻松实现质的飞跃! 视频号新流量入口,微信问一问。如何玩转问一问功能,手把手操作教学。#视频号#微信#问一问#短视频#直播 市面上还有这么牛逼的一个流量隐藏入口,先看一下数据&#x…

算法工程师重生之第六天(四数相加II 赎金信 三数之和 四数之和 总结 )

参考文献 代码随想录 一、四数相加 II 给你四个整数数组 nums1、nums2、nums3 和 nums4 &#xff0c;数组长度都是 n &#xff0c;请你计算有多少个元组 (i, j, k, l) 能满足&#xff1a; 0 < i, j, k, l < nnums1[i] nums2[j] nums3[k] nums4[l] 0 示例 1&#x…

《Rocky Linux 的下载和安装详细步骤》

以下是 Rocky Linux 的下载和安装详细步骤&#xff1a; 下载&#xff1a; 1. 打开 Rocky Linux 的官方网站&#xff1a;https://rockylinux.org/ 2. 在首页找到“Download”&#xff08;下载&#xff09;选项&#xff0c;点击进入下载页面。 3. 在下载页面中&#xff0c;您…

【鸿蒙 HarmonyOS NEXT】使用屏幕属性display:获取屏幕宽高

✨本人自己开发的开源项目&#xff1a;土拨鼠充电系统 ✨踩坑不易&#xff0c;还希望各位大佬支持一下&#xff0c;在GitHub给我点个 Start ⭐⭐&#x1f44d;&#x1f44d; ✍GitHub开源项目地址&#x1f449;&#xff1a;https://github.com/cheinlu/groundhog-charging-syst…

three.js工厂案例

最终效果图 给定html页面&#xff0c;作为tag标签展示 <body style"background-color: black;"><div id"tag"><div style"position:relative;width:400px;height:322px;color: #fff;"><img src"background.png"…

Vue3+TS项目封装一个公共的el-table组件二次封装

前言 支持动态传入列&#xff0c;列内容可以指定插槽&#xff0c;指定格式化显示 样式没太写&#xff0c;主要分享基础功能封装 效果 Table组件代码BaseTable.vue <template><el-table :data"data" border><template v-for"col in columns&q…

基于SpringBoot+Vue+MySQL的画师约稿平台系统

系统展示 用户界面 画师界面 管理员界面 系统背景 基于SpringBootVueMySQL的画师约稿平台系统的背景&#xff0c;主要源于数字艺术行业的快速发展与画师、客户双方需求的日益增长。在传统的约稿方式中&#xff0c;往往存在沟通效率低下、交易过程不透明等问题&#xff0c;这限制…

Pandas读取某列、某行数据——loc、iloc区别

loc&#xff1a;通过行、列的名称或标签来索引 iloc&#xff1a;通过行、列的索引位置来寻找数据 首先&#xff0c;我们先创建一个DataFrame生成数据 import pandas as pddata {a:[1,2,3,4,5],b:[6,7,8,9,10],c:[11,12,13,14,15] } data pd.DataFrame(data) print(data) 运行…

IO模型---BIO、NIO、IO多路复用、AIO详解

本篇将想给详细解释一下什么是BIO、NIO、IO多路复用以及AIO~ 同步的阻塞(BIO)和非阻塞(NIO)的区别 BIO&#xff1a;线程发来IO请求后&#xff0c;一直阻塞着IO线程&#xff0c;需要缓冲区这边数据准备好之后&#xff0c;才会进行下一步的操作。 举个&#x1f330;&#xff1…

2024秋季云曦开学考

web ezezssrf 打开环境&#xff0c;代码审计 看起来有点多&#xff0c;要绕过五层 第一层&#xff1a;存在弱比较&#xff0c;使用数组或0e绕过 yunxi[]1&wlgf[]2 yunxis878926199a&wlgfs155964671a 第二层&#xff1a;存在强比较&#xff0c;此处使用string限制…

c++(继承、模板进阶)

一、模板进阶 1、非类型模板参数 模板参数分类类型形参与非类型形参。 类型形参即&#xff1a;出现在模板参数列表中&#xff0c;跟在class或者typename之类的参数类型名称。 非类型形参&#xff0c;就是用一个常量作为类(函数)模板的一个参数&#xff0c;在类(函数)模板中…

装饰器模式decorator

学习笔记&#xff0c;原文链接 https://refactoringguru.cn/design-patterns/decorator 将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为 调用过程 当你调用 encoded.writeData(salaryRecords); 时&#xff0c;控制流首先进入 CompressionDecorator 的 writeData …

微信小程序原生支持TS、LESS、SASS能力探究

文章目录 原生支持开始使用旧项目新建项目TS声明文件更新 功能说明less 使用全局变量sass 使用全局变量 可以参考原文 在之前开发小程序中&#xff0c;无法使用 less/sass 等 css 预编译语言&#xff0c;也无法使用 TS 进行开发&#xff0c;但在最新的编辑器版本中&#xff0c…

面向对象程序设计之模板进阶(C++)

在之前我出过一篇博客介绍了模版的初阶:面向对象程序设计(C)模版初阶&#xff0c;接下来我们将进行模版的进阶学习&#xff0c;介绍关于更多模版的知识 1.非类型模版参数 模板参数分类类型形参与非类型形参 类型形参即&#xff1a;出现在模板参数列表中&#xff0c;跟在class或…

如何处理DDOS攻击问题

随着信息技术的飞速发展&#xff0c;网络已成为现代社会不可或缺的一部分&#xff0c;极大地便利了个人社交和商业活动。然而&#xff0c;网络空间在创造无限机遇的同时&#xff0c;也潜藏着诸多威胁&#xff0c;其中分布式拒绝服务攻击&#xff08;DDoS&#xff0c;Distribute…

利用Idea远程调试

注意&#xff1a;远程调试不要应用在正式环境中&#xff0c;即便是测试环境也只建议在开发网段使用 在实际的开发过程中&#xff0c;为了验证测试环境的一些问题&#xff0c;且问题在本地不可复现&#xff0c;可以使用远程debug的形式来进行问题定位&#xff0c;而不用循环进行…