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

  1. try {
  2. Class cls = Chicken.class; // 获得Chicken类的基因类型
  3. // 通过字段名称获取该类的字段对象
  4. Field sexField = cls.getDeclaredField("sex");
  5. } catch (NoSuchFieldException e) { // 捕捉到了无此字段异常
  6. e.printStackTrace();
  7. } catch (SecurityException e) { // 捕捉到了安全异常
  8. e.printStackTrace();
  9. }

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

  1. if (sexField != null) {
  2. sexField.setAccessible(true); // 将该字段设置为允许访问
  3. try {
  4. sex = sexField.getInt(chicken); // 获取某实例的字段值
  5. } catch (IllegalArgumentException e) { // 捕捉到了非法参数异常
  6. e.printStackTrace();
  7. } catch (IllegalAccessException e) { // 捕捉到了非法入口异常
  8. e.printStackTrace();
  9. }
  10. }

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

  1. // 通过反射来获得某个实例的私有属性
  2. private static int getReflectSex(Chicken chicken) {
  3. int sex = -1;
  4. try {
  5. Class cls = Chicken.class; // 获得Chicken类的基因类型
  6. // 通过字段名称获取该类的字段对象
  7. Field sexField = cls.getDeclaredField("sex");
  8. if (sexField != null) {
  9. sexField.setAccessible(true); // 将该字段设置为允许访问
  10. sex = sexField.getInt(chicken); // 获取某实例的字段值
  11. }
  12. } catch (Exception e) { // 捕捉到了任何一种异常(错误除外)
  13. e.printStackTrace();
  14. }
  15. return sex;
  16. }

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

  1. // 通过反射来修改某个实例的私有属性
  2. private static void setReflectSex(Chicken chicken, int sex) {
  3. try {
  4. Class cls = Chicken.class; // 获得Chicken类的基因类型
  5. // 通过字段名称获取该类的字段对象
  6. Field sexField = cls.getDeclaredField("sex");
  7. if (sexField != null) {
  8. sexField.setAccessible(true); // 将该字段设置为允许访问
  9. sexField.setInt(chicken, sex); // 将某实例的该字段修改为指定数值
  10. }
  11. } catch (Exception e) { // 捕捉到了任何一种异常(错误除外)
  12. e.printStackTrace();
  13. }
  14. }

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

  1. Cock cock = new Cock(); // 创建一个公鸡实例
  2. System.out.println("准备修理公鸡,性别取值 = "+getReflectSex(cock));
  3. setReflectSex(cock, cock.FEMALE); // 把公鸡实例的性别篡改为“雌性”
  4. System.out.println("结束修理公鸡,性别取值 = "+getReflectSex(cock));
  5. Hen hen = new Hen(); // 创建一个母鸡实例
  6. System.out.println("准备修理母鸡,性别取值 = "+getReflectSex(hen));
  7. setReflectSex(hen, hen.MALE); // 把母鸡实例的性别篡改为“雄性”
  8. System.out.println("结束修理母鸡,性别取值 = "+getReflectSex(hen));

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

  1. 准备修理公鸡,性别取值 = 0
  2. 结束修理公鸡,性别取值 = 1
  3. 准备修理母鸡,性别取值 = 1
  4. 结束修理母鸡,性别取值 = 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. Java内存模型锦集

    [内存操作与内存屏障] 内存模型操作: lock(锁定) : 作用与主内存的变量, 它把一个变量标识为一条线程独占的状态 unlock(解锁) : 作用于主内存变量, 它把一个处于锁定状态的变量释放出 ...

  2. MemCache详细解读

    MemCache是什么 MemCache是一个自由.源码开放.高性能.分布式的分布式内存对象缓存系统,用于动态Web应用以减轻数据库的负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高 ...

  3. Python程序里的注释和#号

    Python程序里的注释是很重要的.它们可以用自然语言告诉你某段代码的功能是什么.在你想要临时移除一段代码时,你还可以用注解的方式将这段代码临时禁用.接下来的练习将让你学会注释 : # A comme ...

  4. #Java学习之路——基础阶段(第八篇)

    我的学习阶段是跟着CZBK黑马的双源课程,学习目标以及博客是为了审查自己的学习情况,毕竟看一遍,敲一遍,和自己归纳总结一遍有着很大的区别,在此期间我会参杂Java疯狂讲义(第四版)里面的内容. 前言: ...

  5. [Swift]LeetCode877. 石子游戏 | Stone Game

    Alex and Lee play a game with piles of stones.  There are an even number of piles arranged in a row, ...

  6. [Swift]LeetCode899. 有序队列 | Orderly Queue

    A string S of lowercase letters is given.  Then, we may make any number of moves. In each move, we c ...

  7. [Swift]LeetCode999. 车的可用捕获量 | Available Captures for Rook

    在一个 8 x 8 的棋盘上,有一个白色车(rook).也可能有空方块,白色的象(bishop)和黑色的卒(pawn).它们分别以字符 “R”,“.”,“B” 和 “p” 给出.大写字符表示白棋,小写 ...

  8. [Abp 源码分析]九、事件总线

    0.简介 事件总线就是订阅/发布模式的一种实现,本质上事件总线的存在是为了降低耦合而存在的. 从上图可以看到事件由发布者发布到事件总线处理器当中,然后经由事件总线处理器调用订阅者的处理方法,而发布者和 ...

  9. 【Redis篇】初始Redis与Redis安装

    一.前述 Redis是当前比较热门的NOSQL系统之一,它是一个key-value存储系统.和Memcache类似,但很大程度补偿了Memcache的不足,它支持存储的value类型相对更多,包括st ...

  10. 5.jQuery

    简介 jQuery是一个快速.简洁的JavaScript框架,jQuery设计的宗旨是“write Less,Do More”,即倡导写更少的代码,做更多的事情.它封装JavaScript常用的功能代 ...