早在介绍多态的时候,曾经提到公鸡实例的性别属性可能被篡改为雌性,不过面向对象的三大特性包含了封装、继承和多态,只要把性别属性设置为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开发笔记(序)章节目录

Java开发笔记(七十九)利用反射技术操作私有属性的更多相关文章

  1. Java开发笔记(八十)利用反射技术操作私有方法

    前面介绍了如何利用反射技术读写私有属性,不单是私有属性,就连私有方法也能通过反射技术来调用.为了演示反射的逆天功能,首先给Chicken鸡类增加下列几个私有方法,简单起见弄来了set***/get** ...

  2. Java开发笔记(十九)规律变化的for循环

    前面介绍while循环时,有个名叫year的整型变量频繁出现,并且它是控制循环进出的关键要素.不管哪一种while写法,都存在三处与year有关的操作,分别是“year = 0”.“year<l ...

  3. Java开发笔记(十三)利用关系运算符比较大小

    前面在<Java开发笔记(九)赋值运算符及其演化>中提到,Java编程中的等号“=”表示赋值操作,并非数学上的等式涵义.Java通过等式符号“==”表示左右两边相等,对应数学的等号“=”: ...

  4. Java开发笔记(九十九)定时器与定时任务

    前面介绍了线程的几种运行方式,不管哪种方式,一旦调用了线程实例的start方法,都会立即启动线程的事务处理.然而某些业务场景在事务执行时间方面有特殊需求,例如期望延迟若干时间之后才开始事务运行,又如期 ...

  5. Java笔记(十九) 反射

    反射 反射是在运行时获取类型的信息,再根据这些信息进行操作. 一.Class类 每个已加载的类在内存中都有一份类信息,每个对象都有指向它的类信息的引用. 在Java中,类信息对应的类就是java.la ...

  6. Java开发笔记(十二)布尔变量论道与或非

    在编程语言的设计之初,它们除了可以进行数学计算,还常常用于逻辑推理和条件判断.为了实现逻辑判断的功能,Java引入了一种布尔类型boolean,用来表示“真”和“假”.该类型的变量只允许两个取值,即t ...

  7. Java开发笔记(十六)非此即彼的条件分支

    前面花了大量篇幅介绍布尔类型及相应的关系运算和逻辑运算,那可不仅仅是为了求真值或假值,更是为了通过布尔值控制流程的走向.在现实生活中,常常需要在岔路口抉择走去何方,往南还是往北,向东还是向西?在Jav ...

  8. .Net开发笔记(十九) 创建一个可以可视化设计的对象

    阅读本篇博客之前需要了解VS窗体设计器的工作原理,详细可参见本系列博客(十).(十一).(十二).必须需要知道的一条结论就是:处于窗体设计器(Form Designer)中的任何组件(包含控件,下同) ...

  9. Java学习笔记(十九)——Java 日志记录 AND log4j

    [前面的话] 学习的进度应该稍微在快一点. Java日志到了必须学习怎么使用的时候了,因为在项目中要进行使用.基础性文章,选择性阅读. [结构] java日志对调试,记录运行,问题定位都起到了很重要的 ...

随机推荐

  1. cadence原理图设计

  2. ueditorUE 去掉本地保存成功的提示框!

    网上修改什么JS的太麻烦,这样比较暴力,仅供参考 这里直接修改的样式: ue.ready(function () {                 $(".edui-editor-mess ...

  3. js-day05-JSON-jQuery初体验

    JSON数据格式 JSON(JavaScript Object Notation)一种简单的数据格式,比xml更轻巧.易于人阅读和编写,同时也易于机器解析和生成(网络传输速度快)JSON是JavaSc ...

  4. 微信小程序开发-窗体设置

    "window": { "backgroundTextStyle": "light", "navigationBarBackgro ...

  5. ASP.NET Core应用的错误处理[4]:StatusCodePagesMiddleware中间件如何针对响应码呈现错误页面

    StatusCodePagesMiddleware中间件与ExceptionHandlerMiddleware中间件比较类似,它们都是在后续请求处理过程中“出错”的情况下利用一个错误处理器来完成最终的 ...

  6. 【从零开始搭建自己的.NET Core Api框架】(一)创建项目并集成swagger:1.1 创建

    系列目录 一.  创建项目并集成swagger 1.1 创建 1.2 完善 二. 搭建项目整体架构 三. 集成轻量级ORM框架——SqlSugar 3.1 搭建环境 3.2 实战篇:利用SqlSuga ...

  7. [Swift]LeetCode202. 快乐数 | Happy Number

    Write an algorithm to determine if a number is "happy". A happy number is a number defined ...

  8. [Swift]LeetCode565. 数组嵌套 | Array Nesting

    A zero-indexed array A of length N contains all integers from 0 to N-1. Find and return the longest ...

  9. 本地安装MySQL详细教程

    第1章   MySQL的安装与使用 1.1  MySQL安装与配置 1.1.1 MYSQL的安装 1.打开下载的mysql安装文件mysql-5.5.27-win32.zip,双击解压缩,运行“set ...

  10. python编译pyc工程--导包问题解决

    利用python 编译工程,生产pyc文件 pyc文件好处:是一种二进制机器码,并且隐藏了源文件代码,但是有和py文件一样的功能(可以理解为效果一样) 所以可以将代码隐藏,便于商业价值,保护代码隐私还 ...