早在介绍多态的时候,曾经提到公鸡实例的性别属性可能被篡改为雌性,不过面向对象的三大特性包含了封装、继承和多态,只要把性别属性设置为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. mysql数据库备份_可执行文件

    这段时间接手运维的工作,刚开始就尝到了数据丢失的痛!老板抱怨,同事抱怨!都说先删库再跑路,我还不想跑! 下面是我的备份记录:(分4步) 1.编写备份执行文件sqlAutoBak.sh #!/bin/s ...

  2. nova boot from volume代码分析

    首先要创建一个bootable volume curl -i http://16.158.166.197:8776/v1/c24c59846a7f44538d958e7548cc74a3/volume ...

  3. zepto与jquery冲突的解决

    一般是不会把zepto和jquery一起来用的.但有时候要引入一些插件,可能就会遇到这样的问题. jquery noConflict() jquery有一个方法叫noConflict() ,可以把jq ...

  4. SVG PATH 生成器

    参考网站:http://dayu.pw/svgcontrol/ 主要功能:手动可视化生成 SVG图片PATH路径. 效果如下: 代码如下: <!DOCTYPE html> <!-- ...

  5. [Swift]LeetCode324. 摆动排序 II | Wiggle Sort II

    Given an unsorted array nums, reorder it such that nums[0] < nums[1] > nums[2] < nums[3]... ...

  6. [Swift]LeetCode345. 反转字符串中的元音字母 | Reverse Vowels of a String

    Write a function that takes a string as input and reverse only the vowels of a string. Example 1: In ...

  7. Java数据结构与算法 - 外部存储

    Q: 什么是外部存储? A: 外部存储特指某类磁盘系统,例如在大多数台式电脑或服务器中的硬盘. Q: 如何访问外部存储? A: 我们所学的数据结构都是假设数据存储在内存中,但是,在很多情况下要处理的数 ...

  8. windows下golang实现Kfaka消息发送及kafka环境搭建

    kafka环境搭建: 一.安装配置java-jdk (1)kafka需要java环境,安装java-jdk,下载地址:https://www.oracle.com/technetwork/java/j ...

  9. SecureCRT8.1发送命令到多个或所有linux终端

  10. 用Flutter开发的跨平台项目,完美运行在Android和IOS上,Material简洁风格,包括启动页、引导页、注册、登录、首页、体系、公众号、导航、项目,还有漂亮的妹子图库,运行极度流畅,结构清晰,代码规范,值得拥有

    Flutter学习资源汇总持续更新中...... Flutter官方网站 Flutter中文网 wendux的Flutter实战 Flutter官方exampleflutter_gallery 阿里巴 ...