欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java开发笔记(七十九)利用反射技术操作私有属性

程序员文章站 2022-04-28 10:26:00
早在介绍多态的时候,曾经提到公鸡实例的性别属性可能被篡改为雌性,不过面向对象的三大特性包含了封装、继承和多态,只要把性别属性设置为private私有级别,也不提供setSex这样的性别修改方法,那么性别属性就被严严实实地封装了起来,不但外部无法修改性别属性,连公鸡类的子类都无法修改。如此一来,公鸡实 ......

早在介绍多态的时候,曾经提到公鸡实例的性别属性可能被篡改为雌性,不过面向对象的三大特性包含了封装、继承和多态,只要把性别属性设置为private私有级别,也不提供setsex这样的性别修改方法,那么性别属性就被严严实实地封装了起来,不但外部无法修改性别属性,连公鸡类的子类都无法修改。如此一来,公鸡实例的性别属性可谓防护周全,压根不存在被篡改的可能性。但是java给面向对象留了个后门,也就是反射技术,利用反射技术竟然能够攻破封装的防护网,使得篡改私有属性从理想变成了现实,赶紧来看看反射技术是怎样做到这点的。
上一篇文章讲到通过字符串可以获得该串所代表的class对象,那么通过字段名称字符串也能获得对应的字段对象,其中的获取操作用到了class对象的getdeclaredfield方法,完整的字段对象获取代码如下所示:

		try {
			class cls = chicken.class; // 获得chicken类的基因类型
			// 通过字段名称获取该类的字段对象
			field sexfield = cls.getdeclaredfield("sex");
		} catch (nosuchfieldexception e) { // 捕捉到了无此字段异常
			e.printstacktrace();
		} catch (securityexception e) { // 捕捉到了安全异常
			e.printstacktrace();
		}

 

注意调用getdeclaredfield方法之时需要捕捉两种异常,包括无此字段异常nosuchfieldexception和安全异常securityexception。现在得到的field对象便隐藏着sex属性的内在信息,要想从field对象挖掘出sex属性的数值,还得继续下列两个步骤的处理:
1、调用field对象的setaccessible方法,并传入true值,表示将该字段设置为允许访问,以解除private的限制;
2、调用field对象的getint方法,并传入鸡类实例,表示准备从该示例中获取指定字段的整型值。同理调用getboolean方法获取的是布尔值,调用getstring方法获取的是字符串值。倘若是获取基本类型以外的类型值,则需先调用get方法获得object对象,再强制转换为目标类型。
整合以上的两个处理步骤,得到以下的字段数值获取代码:

			if (sexfield != null) {
				sexfield.setaccessible(true); // 将该字段设置为允许访问
				try {
					sex = sexfield.getint(chicken); // 获取某实例的字段值
				} catch (illegalargumentexception e) { // 捕捉到了非法参数异常
					e.printstacktrace();
				} catch (illegalaccessexception e) { // 捕捉到了非法入口异常
					e.printstacktrace();
				}
			}

 

注意字段对象的getint方法在调用时也要捕捉两种异常,包括非法参数异常illegalargumentexception,以及非法入口异常illegalaccessexception。这里的两种异常加上之前调用getdeclaredfield方法的两种异常,寥寥数行的反射代码竟要手工捕捉四种异常,未免太大动干戈了。其实程序员可以相信自己,保证反射过程中的操作代码完全正确,这样便无需逐个捕捉某种异常,只要一次性捕捉总的异常即exception就行了。于是简化了异常捕捉逻辑的反射代码变成了下面这般:

	// 通过反射来获得某个实例的私有属性
	private static int getreflectsex(chicken chicken) {
		int sex = -1;
		try {
			class cls = chicken.class; // 获得chicken类的基因类型
			// 通过字段名称获取该类的字段对象
			field sexfield = cls.getdeclaredfield("sex");
			if (sexfield != null) {
				sexfield.setaccessible(true); // 将该字段设置为允许访问
				sex = sexfield.getint(chicken); // 获取某实例的字段值
			}
		} catch (exception e) { // 捕捉到了任何一种异常(错误除外)
			e.printstacktrace();
		}
		return sex;
	}

 

然而上面的代码仅仅通过反射取到性别字段的数值,仍旧没能修改该字段的数值,若想真正改变性别字段的取值,需要把getint方法改为setint方法,并给setint方法的第二个参数传入修改后的数值。此时利用反射技术篡改字段值的代码示例如下:

	// 通过反射来修改某个实例的私有属性
	private static void setreflectsex(chicken chicken, int sex) {
		try {
			class cls = chicken.class; // 获得chicken类的基因类型
			// 通过字段名称获取该类的字段对象
			field sexfield = cls.getdeclaredfield("sex");
			if (sexfield != null) {
				sexfield.setaccessible(true); // 将该字段设置为允许访问
				sexfield.setint(chicken, sex); // 将某实例的该字段修改为指定数值
			}
		} catch (exception e) { // 捕捉到了任何一种异常(错误除外)
			e.printstacktrace();
		}
	}

 

从上述的setreflectsex代码可知,该方法传入一个鸡类实例和新的性别,目的是把这只鸡的性别变过来。这下有了getreflectsex方法可读取性别属性,还有setreflectsex方法可写入性别属性,再由外部接连调用这两个方法,从而验证反射技术的执行效果。下面是外部先后篡改公鸡实例性别、篡改母鸡实例性别的演示代码:

		cock cock = new cock(); // 创建一个公鸡实例
		system.out.println("准备修理公鸡,性别取值 = "+getreflectsex(cock));
		setreflectsex(cock, cock.female); // 把公鸡实例的性别篡改为“雌性”
		system.out.println("结束修理公鸡,性别取值 = "+getreflectsex(cock));
		hen hen = new hen(); // 创建一个母鸡实例
		system.out.println("准备修理母鸡,性别取值 = "+getreflectsex(hen));
		setreflectsex(hen, hen.male); // 把母鸡实例的性别篡改为“雄性”
		system.out.println("结束修理母鸡,性别取值 = "+getreflectsex(hen));

 

运行以上的演示代码,观察到下列的日志描述:

准备修理公鸡,性别取值 = 0
结束修理公鸡,性别取值 = 1
准备修理母鸡,性别取值 = 1
结束修理母鸡,性别取值 = 0

 

可见尽管鸡类的sex属性被声明为private,但是公鸡实例的性别依然被篡改为雌性,母鸡实例的性别依然被篡改为雄性了。



更多java技术文章参见《java开发笔记(序)章节目录