问题, 以及一个解决方式
今天公司的 JAVA 项目碰到一个问题: 在生成 xls 文件的时候. 假设数据较多. 会出现 ArrayIndexOutOfBoundsException.
Google 发现是项中所用的 jxl 包 (开源库, 用以处理 xls 文件) 的一个 BUG.
也找到了一个解决的方法:-- 即找到它的源码. 改动当中的一个静态常量. 然后又一次打包成 jar 就可以.
试了一下, 这种方法确实可行.
还有一个解决方式 -- 反射
只是后来在公司前辈提醒, 能够试一下 --
利用 java 的反射, 在执行时将须要改动的常量强制更改成我们所须要的值
-- 这样就不用改动 jxl 库了, 仅仅要在我们项目中加几句就 OK 了. 出问题的概率也会小非常多.
于是就研究了一下, 尽管最后还是发如今这种方法在我们的项目不可行, 只是还是非常有收获的.
首先, 利用反射改动私有静态常量的方法
对例如以下 Bean 类. 当中的 INT_VALUE 是私有静态常量
- class Bean{
- private static final Integer INT_VALUE = 100;
- }
改动常量的核心代码:
- System.out.println(Bean.INT_VALUE);
- // 获取 Bean 类的 INT_VALUE 字段
- Field field = Bean.class.getField("INT_VALUE");
- // 将字段的訪问权限设为 true: 即去除 private 修饰符的影响
- field.setAccessible(true);
- /* 去除 final 修饰符的影响, 将字段设为可改动的 */
- Field modifiersField = Field.class.getDeclaredField("modifiers");
- modifiersField.setAccessible(true);
- modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
- // 把字段值设为 200
- field.set(null, 200);
- System.out.println(Bean.INT_VALUE);
以上代码输出的结果是:
100
200
说明用反射私有静态常量成功了.
方案的局限
注意到上述代码的中的静态常量类型是 Integer-- 可是我们项目中实际须要改动的字段类型并非包装类型 Integer. 而是 java 的基本类型 int.
当把常量的类型改成 int 之后,
- class Bean{
- private static final int INT_VALUE = 100;// 把类型由 Integer 改成了 int
- }
在其它代码都不变的情况下, 代码输出的结果居然变成了诡异的:
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 , 代码
- if( index> maxFormatRecordsIndex ){
- index = maxFormatRecordsIndex ;
- }
这段代码在编译的时候已经被 java 自己主动优化成这样的:
- if( index> 100){
- index = 100;
- }
所以在 INT_VALUE 是 int 类型的时候
- System.out.println(Bean.INT_VALUE);
- // 编译时会被优化成以下这样:
- System.out.println(100);
所以. 自然, 不管怎么改动 Boolean.INT_VALUE,System.out.println(Bean.INT_VALUE)都还是会依旧固执地输出 100 了.
-- 这本身是 JVM 的优化代码提高执行效率的一个行为, 可是就会导致我们在用反射改变此常量值时出现相似不生效的错觉.
这大概是 JAVA 反射的一个局限吧 -- 改动基本类型的常量时. 不是太可靠.
附一下我測试时候的 DEMO 吧
代码
- import java.lang.reflect.Field;
- import java.lang.reflect.Modifier;
- import java.util.Date;
- public class ForClass {
- static void setFinalStatic(Field field, Object newValue) throws Exception {
- field.setAccessible(true);
- Field modifiersField = Field.class.getDeclaredField("modifiers");
- modifiersField.setAccessible(true);
- modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
- field.set(null, newValue);
- }
- public static void main(String args[]) throws Exception {
- System.out.println(Bean.INT_VALUE);
- setFinalStatic(Bean.class.getField("INT_VALUE"), 200);
- System.out.println(Bean.INT_VALUE);
- System.out.println("------------------");
- System.out.println(Bean.STRING_VALUE);
- setFinalStatic(Bean.class.getField("STRING_VALUE"), "String_2");
- System.out.println(Bean.STRING_VALUE);
- System.out.println("------------------");
- System.out.println(Bean.BOOLEAN_VALUE);
- setFinalStatic(Bean.class.getField("BOOLEAN_VALUE"), true);
- System.out.println(Bean.BOOLEAN_VALUE);
- System.out.println("------------------");
- System.out.println(Bean.OBJECT_VALUE);
- setFinalStatic(Bean.class.getField("OBJECT_VALUE"), new Date());
- System.out.println(Bean.OBJECT_VALUE);
- }
- }
- class Bean {
- public static final int INT_VALUE = 100;
- public static final Boolean BOOLEAN_VALUE = false;
- public static final String STRING_VALUE = "String_1";
- public static final Object OBJECT_VALUE = "234";
- }
代码输出
- 100
- 100
- ------------------
- String_1
- String_1
- ------------------
- false
- true
- ------------------
- 234
- Fri Apr 25 00:55:05 CST 2014
说明
-- 当中的 Boolean 跟 Object 类型常量被正确改动了, 而基本类型 int 和 String 的改动则 "没有生效".
同步发表在? http://www.barryzhang.com/archives/188
广告一下我的新博客, 欢迎訪问哈~:BarryZhang.com http://www.barryzhang.com/ ??
来源: http://www.bubuko.com/infodetail-3005782.html