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

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

		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属性的数值,还得继续下列两个步骤的处理:
一、调用Field对象的setAccessible方法,并传入true值,表示将该字段设置为容许访问,以解除private的限制;
二、调用Field对象的getInt方法,并传入鸡类实例,表示准备从该示例中获取指定字段的整型值。同理调用getBoolean方法获取的是布尔值,调用getString方法获取的是字符串值。假若是获取基本类型之外的类型值,则需先调用get方法得到Object对象,再强制转换为目标类型。
整合以上的两个处理步骤,获得如下的字段数值获取代码:java

			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));

 

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

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

 

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



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

相关文章
相关标签/搜索