黑马程序员-java学习笔记-反射

本文深入讲解Java反射机制,包括Constructor、Field、Method类的使用,以及如何通过反射创建对象、调用方法、获取成员变量等内容。

---------------------- android培训java培训、期待与您交流! ----------------------

反射就是把Java类中的各种成分映射成相应的java类。例如,一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等等。 

反射学习要点

就是学会运用反射的API来获取一个类中各个成员对象,如成员变量、构造方法和普通方法,然后运用获取的对象进行操作。


1、Constructor类

通常我们编写一个类,会写一个构造函数,通过反射如何获取一个类的构造函数呢?Constructor类代表某个类中的一个构造方法

要获取一个类的构造函数,必须先获取该类的实例,这时就用到了Class类,再通过Class类的getConstructor()方法,我们可以这样获取一个Constructor对象

Constructor constructor = Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);

或者这样子

String.class.getConstructor(StringBuffer.class)
获取带两个参数的构造函数

String.class.getConstructor(StringBuffer.class,int.class)
为什么该方法可以接收一个、两个或者多个参数呢?

因为jdk1.5的新特性:可变参数

在jdk1.5之前是用一个数组来实现的

jdk1.4的文档声明如下:


通过getConstructor()方法得到了一个Contructor对象,就可以调用Contructor类的方法
Constructor constructor1 = String.class.getConstructor(StringBuffer.class);
String str2 = (String)constructor1.newInstance("abc");


结果报错了,为什么呢?

这里出现了两个StringBuffer
第一个表示你选择了哪个构造方法
第二个表示使用刚才选用的StringBuffer构造方法的时候,传递一个StringBuffer对象给该方法

编译时,只知道contructor1是一个构造类对象,它可以newInstance(),但是并不知道
调用了哪个(这里指String,String中有很多构造方法)构造方法,因此也并不知道返回了什么类型
所以这个会报错
String str2 = (String)constructor1.newInstance("abc");
运行时才知道到底调用了哪个构造方法  (通过后面的参数)
Constructor constructor1 = String.class.getConstructor(StringBuffer.class);
这个不会报错
String str2 = (String)constructor1.newInstance(new StringBuffer("abc"));

再通过一个例子进行强化

Constructor constructor1 = String.class.getConstructor(StringBuffer.class);
		String str2 = (String)constructor1.newInstance("abc"));
		System.out.println(str2.charAt(2));

上面代码可以正确编译
但是运行时会报错

因为contructor1对应的构造方法是StringBuffer("一个参数"),而在newInstance()中却传递了一个String类型
而编译器并不知道到底,只知道contructor1是一个构造方法,所以只有在运行的时候,才知道参数错误了,报错。

而Class类也提供了newInstance()方法,它调用的是不带参数的构造方法

Class.newInstance()方法:
例子:

String obj = (String)Class.forName("java.lang.String").newInstance();

该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。
该方法内部的具体代码是怎样写的呢?

用到了缓存机制来保存默认构造方法的实例对象。

总结:反射方式创建一个对象的过程
Class--Constructor----new object

可以看出反射是比较消耗性能的。


2、Field类

Field类代表某个类中的一个成员变量

使用技巧:eclipse生成构造方法的源代码


在介绍Field类之前,我们先编写一个测试类ReflectPoint

public class ReflectPoint {
	private int x;
	public int y;
	
	public ReflectPoint(int x, int y) {
		super();
		this.x = x;
		this.y = y;
	}
}

然后进行调用一下

ReflectPoint pt1 = new ReflectPoint(3,5);
Field fieldY = pt1.getClass().getField("y");

那fieldYd 值是多少呢?是5吗?

结果不是5!

因为fieldY不是对象身上的变量,而是类上,要用它去取某个对象上对应的值

所以 fieldY不代表具体的值,不是pt1身上的变量Y,而是代表ReflectPoint类上,要用它去取某个对象(不如pt1)
所对应的值
要取出fieldY对象所对应的值应该这样
System.out.println(fieldY.get(pt1));
既然知道了取值的方式, 用刚才的方法来取一下x的值试试

Field fieldX = pt1.getClass().getField("x");
System.out.println(fieldX.get(pt1));
结果报错了!


因为x是私有的,如果要去私有的成员变量应该用如下代码
Field fieldX = pt1.getClass().getDeclaredField("x");
System.out.println(fieldX.get(pt1));
然后高兴地点击运行,还是不行啊!报错了


这是需要暴力反射了,通过setAccesible(true)来设置私有成员可以访问
Field fieldX = pt1.getClass().getDeclaredField("x");
fieldX.setAccessible(true);
System.out.println(fieldX.get(pt1));

这里有个需求:将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的"b"改成"a"。

我们对之前编写的ReflectPoint类进行扩展,覆盖该类的toString()方法

public class ReflectPoint {
	private Date birthday = new Date();
	
	private int x;
	public int y;
	public String str1 = "ball";
	public String str2 = "basketball";
	public String str3 = "itcast";
	
	public ReflectPoint(int x, int y) {
		super();
		this.x = x;
		this.y = y;
	
	@Override
       public String toString(){
       return str1 + ":" + str2 + ":" + str3;}
覆盖该类的toString()方法是方便观看结果


接着编写功能方法

private static void changeStringValue(Object obj) throws Exception {
		Field[] fields = obj.getClass().getFields();//获取目标对象的所有成员对象
		for(Field field : fields){//开始遍历
			//if(field.getType().equals(String.class)){
			if(field.getType() == String.class){//成员变量的类型是否是String
				String oldValue = (String)field.get(obj);
				String newValue = oldValue.replace('b', 'a');//a代替b
				field.set(obj, newValue);//将值设置进目标对象里面
			}
		}

因为字节码在内存只存在一份,所以比较字节码时用 == ,显得更加专业!
最后我们来测试一下
ReflectPoint pt1 = new ReflectPoint(3,5);
changeStringValue(pt1);
System.out.println(pt1);
这个方法小小的模拟了一下字段的反射,spring架构也是如此,修改配置文件中的字段。

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

在平时的编码中
调用String类的charAt()方法,是通过构造出该类的对象,通过对象去调用该方法,如
str1.chatAt();
str2.charAt();
从中可以看出,对象与方法并没有绑定,方法是属于类的
利用反射的方式,先获取某个字节码的方法,再用获得的方法去作用某个对象

Method methodCharAt = String.class.getMethod("charAt", int.class);
		System.out.println(methodCharAt.invoke(str1, 1));

getMethod() //第一个参数传递的是方法名字,第二个参数是方法中的参数,这个是可变的
invoke() //第一个参数是指需要调动的对象,第二个是charAt()的参数

这里再次体现了面向对象的思想
methodCharAt表示一个方法对象,invoke是Method类的一个方法,表示调用
就像人在黑板画圆一样,画圆的方法应该由圆提供的,这里的调用方法也是有方法类提供的
静态方法的调用
静态方法不需要传递对象,所以传递一个Null
System.out.println(methodCharAt.invoke(null, 1));
dk1.4和jdk1.5的invoke方法的区别:
Jdk1.5:public Object invoke(Object obj,Object... args)
Jdk1.4:public Object invoke(Object obj,Object[] args),即按jdk1.4的语法,需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,调用charAt方法的代码也可以用Jdk1.4改写为 charAt.invoke(“str”, new Object[]{1})形式。

1.4的调用形式

System.out.println(methodCharAt.invoke(str1, new Object[]{2}));


4、用反射执行某个类的main()方法

按照以往的编码经验,可以使用调用静态方法一样,调用main()方法
TestArguments.main(new String[] {"111","222","333"});
按照上面的知识点我们用反射的方式继续努力调用mian()方法

String startingMethodClassName=args[0];
Method mainMethod=Class.forName(startingMethodClassName.getMethod("main"),String[].class);

args[0]在这里代表一个类的完整名字
注意数组类型的传递,      String[].class
获取方法后调用该方法
mainMethod.invoke(null,new String[]{"111","222","333"});

但是运行会报错, 因为遇到数组会进行拆包 

启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?
按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,数组中的每个元素对应一个参数。

当把一个字符串数组作为参数传递给invoke方法时,javac会到底按照哪种语法进行处理呢?
jdk1.5肯定要兼容jdk1.4的语法,会按jdk1.4的语法进行处理,即把数组打散成为若干个单独的参数。
所以,在给main方法传递参数时,不能使用代码mainMethod.invoke(null,new String[]{“xxx”}),javac只把它当作jdk1.4的语法进行理解,而不把它当作jdk1.5的语法解释,因此会出现参数类型不对的问题。

解决方法     关键字:每一个数组都作为一个对象来看待
mainMethod.invoke(null,new Object[]{new String[]{"111","222","333"}});
解决方法二 利用欺骗手法,让编译器认为不是数组,不进行拆分
mainMethod.invoke(null,(Object)new String[]{"111","222","333"});


5、数组的反射

我们先来看看下面的代码

int [] a1 = new int[3];
		int [] a2 = new int[4];
		int[][] a3 = new int[2][3];
		String [] a4 = new String[3];
		System.out.println(a1.getClass() == a2.getClass());
		System.out.println(a1.getClass() == a4.getClass());
		System.out.println(a1.getClass() == a3.getClass());

如果维数一样,而且类型相同,得到的字节码是一样的
上面代码中,最下面两个语句报错,而第一条语句打印true。

打印这些数组的父类的名字
System.out.println(a1.getClass().getSuperclass().getName());
System.out.println(a4.getClass().getSuperclass().getName());

结果发现都是java.lang.Object

小总结

1)具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
2)代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
3)基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。

分析一下下面的代码

                Object aObj1 = a1;
		Object aObj2 = a4;
		Object[] aObj3 = a1;
		Object[] aObj4 = a3;
		Object[] aObj5 = a4;

Object[] aObj4=a3;表示装了Object[]数组中装着int[] 的一维数组
但是由于a1是基本类型,所以Object[] aObj3=a1则行不通
a4表示String ,父类是Object

Array工具类用于完成对数组的反射操作


实现一个方法,打印Object,如果传递的是数组,打印数组的每个值
private static void printObject(Object obj) {
		Class clazz = obj.getClass();//获取参数的字节码
		if(clazz.isArray()){//判断是否是数组
			int len = Array.getLength(obj);//得到数组的长度
			for(int i=0;i<len;i++){
				System.out.println(Array.get(obj, i));//打印数组
			}
		}else{
			System.out.println(obj);//不是数组正常打印
		}
		
	}

反射实现框架

框架与框架要解决的核心问题

我做房子卖给用户住,由用户自己安装门窗和空调,我做的房子就是框架,用户需要使用我的框架,把门窗插入进我提供的框架中。框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类。

框架要解决的核心问题
我在写框架(房子)时,你这个用户可能还在上小学,还不会写程序呢?我写的框架程序怎样能调用到你以后写的类(门窗)呢?
因为在写才程序时无法知道要被调用的类名,所以,在程序中无法直接new 某个类的实例对象了,而要用反射方式来做。

HashSet的比较以及Hashcode分析
我们继续利用上文提到的ReflectPoint进行分析
               Collection collections = new HashSet();
		ReflectPoint pt1 = new ReflectPoint(3,3);
		ReflectPoint pt2 = new ReflectPoint(5,5);
		ReflectPoint pt3 = new ReflectPoint(3,3);	

		collections.add(pt1);
		collections.add(pt2);
		collections.add(pt3);
		collections.add(pt1);	
		System.out.println(collections.size());

打印结果是3,最后一个没有存进去,那pt1和pt3是否是相等呢?
答案是:不相等。
如果要这个两个对象相等,必须自己去写equals()方法,默认的equals比较的是hashcode的值,该值通常用内存地址值换算出来的。
我们可以在eclipse中,右键生成hashcode()和equals()方法




最后得到代码

@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + x;
		result = prime * result + y;
		return result;
	}


	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		final ReflectPoint other = (ReflectPoint) obj;
		if (x != other.x)
			return false;
		if (y != other.y)
			return false;
		return true;
	}

这样之后,打印结果就是2了。
hashcode()方法的作用
没有覆盖hashcode()方法,那结果会如何呢?
答案:可能是2,可能是3
面试题:java中有内存泄露吗?为什么?举例说明。
可以上面引用这个。
通过反射的方式创建ArrayList和HashSet对象
在config.properties文件中写入
配置文件到底放在哪里呢?      
实际开发中要用完整路径,但完整路径不是硬编码,而是运行的时候出来的


然后反射
这里尽量用继承的方式,如InputStream、Collection
InputStream ips = ReflectTest2.class.getResourceAsStream("/cn/itcast/day1/resources/config.properties");
		Properties props = new Properties();
		props.load(ips);//读取配置文件
		ips.close();
		String className = props.getProperty("className");
		//通过反射的方式得到Collection
		//这里的newInstance()调用的是无参数的构造方法
		Collection collections = (Collection)Class.forName(className).newInstance();
		ReflectPoint pt1 = new ReflectPoint(3,3);
		ReflectPoint pt2 = new ReflectPoint(5,5);
		ReflectPoint pt3 = new ReflectPoint(3,3);	

		collections.add(pt1);
		collections.add(pt2);
		collections.add(pt3);
		collections.add(pt1);			
		System.out.println(collections.size());
用类加载器管理资源和配置文件
配置文件放在classpath所指定的文件
InputStream ips=ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/day1/config.properties");

Class本身也提供了getRourceAsStream()
该方法只需要指定文件名,该方法会自动找该类所在包(cn/itcast/day1)
InputStream ips = ReflectTest2.class.getResourceAsStream("/cn/itcast/day1/resources/config.properties");
如果在该包再建立一个子包(cn/itcast/day1/resource),将配置文件放入其中,该怎么办呢?
则只需要修改成:("resource/config.properties")

这些方法内部都是调用了classLoader()方法

总结:
需把配置文件放到classpath中,
在elipse中开发的时候,建立一个resouce文件放入其中,然后eclipse会自动复制配置文件到
classpath中


---------------------- android培训java培训、期待与您交流! ----------------------

详细请查看:http://edu.youkuaiyun.com

标题SpringBoot智能在线预约挂号系统研究AI更换标题第1章引言介绍智能在线预约挂号系统的研究背景、意义、国内外研究现状及论文创新点。1.1研究背景与意义阐述智能在线预约挂号系统对提升医疗服务效率的重要性。1.2国内外研究现状分析国内外智能在线预约挂号系统的研究与应用情况。1.3研究方法及创新点概述本文采用的技术路线、研究方法及主要创新点。第2章相关理论总结智能在线预约挂号系统相关理论,包括系统架构、开发技术等。2.1系统架构设计理论介绍系统架构设计的基本原则和常用方法。2.2SpringBoot开发框架理论阐述SpringBoot框架的特点、优势及其在系统开发中的应用。2.3数据库设计与管理理论介绍数据库设计原则、数据模型及数据库管理系统。2.4网络安全与数据保护理论讨论网络安全威胁、数据保护技术及其在系统中的应用。第3章SpringBoot智能在线预约挂号系统设计详细介绍系统的设计方案,包括功能模块划分、数据库设计等。3.1系统功能模块设计划分系统功能模块,如用户管理、挂号管理、医生排班等。3.2数据库设计与实现设计数据库表结构,确定字段类型、主键及外键关系。3.3用户界面设计设计用户友好的界面,提升用户体验。3.4系统安全设计阐述系统安全策略,包括用户认证、数据加密等。第4章系统实现与测试介绍系统的实现过程,包括编码、测试及优化等。4.1系统编码实现采用SpringBoot框架进行系统编码实现。4.2系统测试方法介绍系统测试的方法、步骤及测试用例设计。4.3系统性能测试与分析对系统进行性能测试,分析测试结果并提出优化建议。4.4系统优化与改进根据测试结果对系统进行优化和改进,提升系统性能。第5章研究结果呈现系统实现后的效果,包括功能实现、性能提升等。5.1系统功能实现效果展示系统各功能模块的实现效果,如挂号成功界面等。5.2系统性能提升效果对比优化前后的系统性能
在金融行业中,对信用风险的判断是核心环节之一,其结果对机构的信贷政策和风险控制策略有直接影响。本文将围绕如何借助机器学习方法,尤其是Sklearn工具包,建立用于判断信用状况的预测系统。文中将涵盖逻辑回归、支持向量机等常见方法,并通过实际操作流程进行说明。 一、机器学习基本概念 机器学习属于人工智能的子领域,其基本理念是通过数据自动学习规律,而非依赖人工设定规则。在信贷分析中,该技术可用于挖掘历史数据中的潜在规律,进而对未来的信用表现进行预测。 二、Sklearn工具包概述 Sklearn(Scikit-learn)是Python语言中广泛使用的机器学习模块,提供多种数据处理和建模功能。它简化了数据清洗、特征提取、模型构建、验证与优化等流程,是数据科学项目中的常用工具。 三、逻辑回归模型 逻辑回归是一种常用于分类任务的线性模型,特别适用于二类问题。在信用评估中,该模型可用于判断借款人是否可能违约。其通过逻辑函数将输出映射为0到1之间的概率值,从而表示违约的可能性。 四、支持向量机模型 支持向量机是一种用于监督学习的算法,适用于数据维度高、样本量小的情况。在信用分析中,该方法能够通过寻找最佳分割面,区分违约与非违约客户。通过选用不同核函数,可应对复杂的非线性关系,提升预测精度。 五、数据预处理步骤 在建模前,需对原始数据进行清理与转换,包括处理缺失值、识别异常点、标准化数值、筛选有效特征等。对于信用评分,常见的输入变量包括收入水平、负债比例、信用历史记录、职业稳定性等。预处理有助于减少噪声干扰,增强模型的适应性。 六、模型构建与验证 借助Sklearn,可以将数据集划分为训练集和测试集,并通过交叉验证调整参数以提升模型性能。常用评估指标包括准确率、召回率、F1值以及AUC-ROC曲线。在处理不平衡数据时,更应关注模型的召回率与特异性。 七、集成学习方法 为提升模型预测能力,可采用集成策略,如结合多个模型的预测结果。这有助于降低单一模型的偏差与方差,增强整体预测的稳定性与准确性。 综上,基于机器学习的信用评估系统可通过Sklearn中的多种算法,结合合理的数据处理与模型优化,实现对借款人信用状况的精准判断。在实际应用中,需持续调整模型以适应市场变化,保障预测结果的长期有效性。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值