【转】java 反射的局限性

标签: java 反射 | 发表时间:2015-03-18 20:23 | 作者:RoomFourteen224
出处:http://www.iteye.com

问题,以及一个解决方案

今天公司的JAVA项目碰到一个问题:在生成xls文件的时候,如果数据较多,会出现ArrayIndexOutOfBoundsException。
Google发现是项中所用的jxl包(开源库,用以处理xls文件)的一个BUG。
也找到了一个解决办法: http://www.blogjava.net/reeve/archive/2013/01/11/114564.html——即找到它的源代码,修改其中的一个静态常量,然后重新打包成jar即可。试了一下,这个方法确实可行。


另一个解决方案——反射

不过后来在公司前辈提醒,可以试一下——

利用java的反射,在运行时将需要修改的常量强制更改成我们所需要的值

——这样就不用修改jxl库了,只要在我们项目中加几句就OK了,出问题的概率也会小很多。
于是就研究了一下,虽然最后还是发现在这个方法在我们的项目不可行,不过还是很有收获的。

首先,利用反射修改私有静态常量的方法

对如下Bean类,其中的INT_VALUE是私有静态常量
[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
 
  1. class Bean{  
  2.     private static final Integer INT_VALUE = 100;  
  3. }  
修改常量的核心代码:
[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
 
  1. System.out.println(Bean.INT_VALUE);  
  2. //获取Bean类的INT_VALUE字段  
  3. Field field = Bean.class.getField("INT_VALUE");  
  4. //将字段的访问权限设为true:即去除private修饰符的影响  
  5. field.setAccessible(true);  
  6. /*去除final修饰符的影响,将字段设为可修改的*/  
  7. Field modifiersField = Field.class.getDeclaredField("modifiers");  
  8. modifiersField.setAccessible(true);  
  9. modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);  
  10. //把字段值设为200  
  11. field.set(null, 200);  
  12. System.out.println(Bean.INT_VALUE);  

以上代码输出的结果是:

100
200

说明用反射私有静态常量成功了。


方案的局限

注意到上述代码的中的静态常量类型是Integer——但是我们项目中实际需要修改的字段类型并不是包装类型Integer,而是java的基本类型int。
当把常量的类型改成int之后,

[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
 
  1. class Bean{  
  2.     private static final int INT_VALUE = 100;//把类型由Integer改成了int  
  3. }  

在其他代码都不变的情况下,代码输出的结果竟然变成了诡异的:

100
100

而且在调试的过程中发现,在第二次输出的时候,内存中的Bean.INT_VALUE是已经变成了200,但是System.out.println(Bean.INT_VALUE)输出的结果却依然时诡异的100?!

——反射失效了吗?

又试了其他几种类型,发现这种貌似失效的情会发生在int、long、boolean以及String这些基本类型上,而如果把类型改成Integer、Long、Boolean这种包装类型,或者其他诸如Date、Object都不会出现失效的情况。

原因

经过一系列的研究、推测、搜索等过程,终于发现了原因:

对于基本类型的静态常量,JAVA在编译的时候就会把代码中对此常量中引用的地方替换成相应常量值。

参考:Modifying final fields in Java
即对于常量 public static final int maxFormatRecordsIndex = 100 ,代码

[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
 
  1. if( index > maxFormatRecordsIndex   ){  
  2.     index  =  maxFormatRecordsIndex ;  
  3. }       

这段代码在编译的时候已经被java自动优化成这样的:

[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
 
  1. if( index > 100){  
  2.     index = 100;  
  3. }  

所以在INT_VALUE是int类型的时候

[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
 
  1. System.out.println(Bean.INT_VALUE);  
  2. //编译时会被优化成下面这样:  
  3. System.out.println(100);  

所以,自然,无论怎么修改Boolean.INT_VALUE,System.out.println(Bean.INT_VALUE)都还是会依然固执地输出100了。

——这本身是JVM的优化代码提高运行效率的一个行为,但是就会导致我们在用反射改变此常量值时出现类似不生效的错觉。

这大概是JAVA反射的一个局限吧——修改基本类型的常量时,不是太可靠。


附一下我测试时候的DEMO吧

代码

[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
 
  1. import java.lang.reflect.Field;  
  2. import java.lang.reflect.Modifier;  
  3. import java.util.Date;  
  4.   
  5. public class ForClass {  
  6.     static void setFinalStatic(Field field, Object newValue) throws Exception {  
  7.         field.setAccessible(true);  
  8.         Field modifiersField = Field.class.getDeclaredField("modifiers");  
  9.         modifiersField.setAccessible(true);  
  10.         modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);  
  11.         field.set(null, newValue);  
  12.     }  
  13.   
  14.     public static void main(String args[]) throws Exception {  
  15.   
  16.         System.out.println(Bean.INT_VALUE);  
  17.         setFinalStatic(Bean.class.getField("INT_VALUE"), 200);  
  18.         System.out.println(Bean.INT_VALUE);  
  19.   
  20.         System.out.println("------------------");  
  21.         System.out.println(Bean.STRING_VALUE);  
  22.         setFinalStatic(Bean.class.getField("STRING_VALUE"), "String_2");  
  23.         System.out.println(Bean.STRING_VALUE);  
  24.           
  25.         System.out.println("------------------");  
  26.         System.out.println(Bean.BOOLEAN_VALUE);  
  27.         setFinalStatic(Bean.class.getField("BOOLEAN_VALUE"), true);  
  28.         System.out.println(Bean.BOOLEAN_VALUE);  
  29.   
  30.         System.out.println("------------------");  
  31.         System.out.println(Bean.OBJECT_VALUE);  
  32.         setFinalStatic(Bean.class.getField("OBJECT_VALUE"), new Date());  
  33.         System.out.println(Bean.OBJECT_VALUE);  
  34.   
  35.     }  
  36. }  
  37.   
  38. class Bean {  
  39.     public static final int INT_VALUE = 100;  
  40.     public static final Boolean BOOLEAN_VALUE = false;  
  41.     public static final String STRING_VALUE = "String_1";  
  42.     public static final Object OBJECT_VALUE = "234";  
  43. }  

代码输出

100
100
------------------
String_1
String_1
------------------
false
true
------------------
234
Fri Apr 25 00:55:05 CST 2014

 

说明

——其中的Boolean跟Object类型常量被正确修改了,而基本类型int和String的修改则“没有生效”。

 

 

转自:http://blog.csdn.net/barryhappy/article/details/24442953/



已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [java 反射] 推荐:

【转】java 反射的局限性

- - Java - 编程语言 - ITeye博客
今天公司的JAVA项目碰到一个问题:在生成xls文件的时候,如果数据较多,会出现ArrayIndexOutOfBoundsException. Google发现是项中所用的jxl包(开源库,用以处理xls文件)的一个BUG. 也找到了一个解决办法: http://www.blogjava.net/reeve/archive/2013/01/11/114564.html——即找到它的源代码,修改其中的一个静态常量,然后重新打包成jar即可.

通过JAVA反射修改JDK1.6*当中DNS缓存内容

- - Taobao QA Team
为了实现性能压测时的域名动态绑定功能,尝试通过java反射修改JDK1.6×当中的DNS缓存,感谢在此过程中林轩同学的大力帮助. 网上也存在着修改DNS缓存的方法,但是都是基于jdk1.5的,无法应用. 另外,大部分都是修改的缓存过期时间,而没有真正去尝试修改dns 的cache内容,所以尝试了很多种方法,并且查看了jdk的源代码,终于实现了修改dns缓存内容和时间,如下,欢迎大家一起探讨.

java中基于线程池和反射机制实现定时任务

- - CSDN博客推荐文章
调用main方法,开始加载任务配置并执行任务. MyTask 类 实现Runnable接口,在main类中调用. TaskModel: 对任务类的封装. XmlReader 任务配置解析类. System.out.println("距离首次运行还差" + initialDelay + "秒. TaskA TaskB TaskC其中定义静态方法 ,这些类的静态方法配置在 xml文件中,被调用.

Java中的锁(Locks in Java)

- - 并发编程网 - ifeve.com
原文链接 作者:Jakob Jenkov 译者:申章 校对:丁一. 锁像synchronized同步块一样,是一种线程同步机制,但比Java中的synchronized同步块更复杂. 因为锁(以及其它更高级的线程同步机制)是由synchronized同步块的方式实现的,所以我们还不能完全摆脱synchronized关键字( 译者注:这说的是Java 5之前的情况).

Java PaaS 对决

- 呆瓜 - IBM developerWorks 中国 : 文档库
本文为 Java 开发人员比较了三种主要的 Platform as a Service (PaaS) 产品:Google App Engine for Java、Amazon Elastic Beanstalk 和 CloudBees RUN@Cloud. 它分析了每种服务独特的技术方法、优点以及缺点,而且还讨论了常见的解决方法.

Java浮点数

- d0ngd0ng - 译言-电脑/网络/数码科技
Thomas Wang, 2000年3月. Java浮点数的定义大体上遵守了二进制浮点运算标准(即IEEE 754标准). IEEE 754标准提供了浮点数无穷,负无穷,负零和非数字(Not a number,简称NaN)的定义. 在Java开发方面,这些东西经常被多数程序员混淆. 在本文中,我们将讨论计算这些特殊的浮点数相关的结果.

Qt——转战Java?

- - 博客 - 伯乐在线
编者按:事实上,在跨平台开发方面,Qt仍是最好的工具之一,无可厚非,但Qt目前没有得到任何主流移动操作系统的正式支持. 诺基亚的未来计划,定位非常模糊,这也是令很多第三方开发者感到失望,因此将导致诺基亚屡遭失败的原因. Qt的主要开发者之一Mirko Boehm在博客上强烈讽刺Nokia裁了Qt部门的决定,称其为“绝望之举”,而非“策略变更”.

java 验证码

- - ITeye博客
// 创建字体,字体的大小应该根据图片的高度来定. // 随机产生160条干扰线,使图象中的认证码不易被其它程序探测到. // randomCode用于保存随机产生的验证码,以便用户登录后进行验证. // 随机产生codeCount数字的验证码. // 得到随机产生的验证码数字. // 产生随机的颜色分量来构造颜色值,这样输出的每位数字的颜色值都将不同.

Java异常

- - CSDN博客推荐文章
“好的程序设计语言能够帮助程序员写出好程序,但是无论哪种语言都避免不了程序员写出坏的程序.                                                                                                                          ----《Java编程思想》.

java面试题

- - Java - 编程语言 - ITeye博客
 抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面. 抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节. 抽象包括两个方面,一是过程抽象,二是数据抽象.  继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法. 对象的一个新类可以从现有的类中派生,这个过程称为类继承.