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??
JAVA反射改動常量,以及其局限