Java开发笔记(七十九)利用反射技术操作私有属性
早在介绍多态的时候,曾经提到公鸡实例的性别属性可能被篡改为雌性,不过面向对象的三大特性包含了封装、继承和多态,只要把性别属性设置为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开发笔记(序)章节目录》
上一篇: PHP正则的Unknown Modifier错误解决方法
下一篇: 博客友情链接的风险和规避