Java 反射修改類的常量值、靜態變數值、屬性值

經典雞翅發表於2021-01-16

前言

有的時候,我們需要修改一個變數的值,但變數也許存在於 Jar 包中或其他位置,導致我們不能從程式碼層面進行修改,於是我們就用到了下面的場景,通過反射來進行修改變數的值。

定義一個實體類

class Bean{  
    private static final Integer INT_VALUE = 100;  
}  

利用反射修改私有靜態常量方法

System.out.println(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

看到測試結果說明我們的反射修改成功了。

利用反射修改共有靜態變數方法

class Bean{  
    public static int nums = 100;
}  

System.out.println(Bean.nums);
Field field = Bean.class.getField("nums");
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的優化程式碼提高執行效率的一個行為,但是就會導致我們在用反射改變此常量值時出現類似不生效的錯覺。

相關文章