JAVA反射改动常量,以及其局限

问题。以及一个解决方式

今天公司的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是私有静态常量
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  


原文地址:https://www.cnblogs.com/jzssuanfa/p/7058007.html