前言
以前寫過一篇 Java 反射修改 final 屬性值,本文將在這里重新溫習一下Java反射如何有效的修改final屬性值,下面話不多說了,來一起看看詳細的介紹:
假設有個類
1
2
3
|
class Person { public final String name = "Mike" ; } |
這里聲明 name 為非靜態的屬性只是為了說明反射修改 final 屬性無關乎靜態不靜態,靜態只是表現在它是一個類屬性,在一個類加載器空間只會有一份拷貝,僅此而已。
創建一個通用方法進行反射修改屬性值
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public static void modify(Object object, String fieldName, Object newFieldValue) throws Exception { Field field = object.getClass().getDeclaredField(fieldName); Field modifiersField = Field. class .getDeclaredField( "modifiers" ); modifiersField.setAccessible( true ); //Field 的 modifiers 是私有的 modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); if (!field.isAccessible()) { field.setAccessible( true ); } field.set(object, newFieldValue); } |
調用 modify(...)
方法試圖修改 person 的 name 屬性
1
2
3
|
Person person = new Person(); modify(person, "name" , "Michael" ); System.out.println(person.name); //輸出仍然是 Mike |
為什么代碼執行下來沒問題,但輸出又還是原來的值呢?但總是可以通過反射方式獲取到修改后的新值。這就是 Java 編譯器對 final 屬型的內聯優化,即編譯時把該 final 的值直接放到了引用它的地方。即使是反射修改了該屬性,但這種事后處理于事無補。
所以我在標題中所說如何有效的修改 final 屬性值是指:反射修改了 final 屬性值后能夠在后續代碼中使用修改后的值。
如果 Person 有個方法
1
2
3
|
public String getName() { return name; } |
在前面代碼反射修改了 name 屬性后,getName()
方法也仍然是返回 "Mike"。
那么 Java 會對什么類型的 final 值進行內聯編譯呢?它們基本類型 byte, char, short, int, long, float, double, boolean; 再加上 Literal String 類型(直接雙引號字符串)。只要是不被編譯器內聯優化的 final 屬性都可以通過反射有效的進行修改(修改后能使用到新的值)
String 類型比較特殊, 如果把 Person 類 name 屬性改成用 new String("Mike")
1
|
public final String name = new String( "Mike" ); |
就能夠有效的使用上面的反射代碼修改 name 的值,最后
1
2
|
modify(person, "name" , "Michael" ); System.out.println(person.name) //輸出就是 Michael 了 |
正如基本類型有相應的包裝類型,可以把 new String("Mike")
想像為 "Mike" 的包裝類型。也確實是基本類型的 final 包裝類型可以被反射有效的修改,new String("Mike")
也可以。
但我們要是在 IDE 中寫成 new String("Mike")
, 可能被告知 new String("")
這種格式是冗余的,IDE 又會把它改成 "Mike",重而反射行為變得不正確。
還有,如果 final 屬性值是通過構造函數傳入的能不能有效的被修改,當然可以,因為它也不會被編譯器內聯優化。
總之一句話:只要不會被編譯器內聯優化的 final 屬性就可以通過反射有效的進行修改 -- 修改后代碼中可使用到新的值
為加深印象,我們最后作一個較全面的測試
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
public class TestFinal { public static void main(String[] args) throws Exception { Person jordan = new Person( "Chicago" ); modify(jordan , "firstName" , "Michael" ); System.out.println(jordan .firstName); //Mike modify(jordan , "lastName" , "Michael" ); System.out.println(jordan.lastName); //Michael modify(jordan , "age" , 51f); System.out.println(jordan .age); //50.5 modify(jordan , "height" , 1 .98f); System.out.println(jordan.height); //1.98 modify(jordan, "address" , new Address( "ccc" , "ddd" )); System.out.println(jordan.address.line1); //ccc modify(jordan , "city" , "Miami" ); System.out.println(jordan.city); //Miami } } class Person { public final String firstName = "Mike" ; public final String lastName = new String( "Jordan" ); //可被有效修改 public final float age = 50 .5f; public final Float height = 1 .99f; //可被有效修改 public final Address address = new Address( "aaa" , "bbb" ); //可被有效修改 public final String city; //可被有效修改 public Person(String city) { this .city = city; } } final class Address { public final String line1; public final String line2; public Address(String line1, String line2) { this .line1 = line1; this .line2 = line2; } } |
我以前也有一人誤區: 就是總以為加了 final 關鍵值,該屬性就會被編譯器內聯優化,就不能用反射有效的進行修改。通過今天的梳理終于清晰的明白了:final 屬性,只要不是基本類型和字面 String,就可以正常使用反射修改它的值。以前曾為了想反射修改某個 final 值還特意把 final 關鍵字給去掉,完全不用這么做。所以對于基本類型和字面 String, final 只是預示著它的值不能被正常的代碼修改。
因此我們在聲明 log 時
1
|
private static Logger log = LoggerFactory.getLogger(TestFinal. class ); |
和
1
|
private static final Logger log = LoggerFactory.getLogger(TestFinal. class ); |
性能上不會有什么差異,也不會因為多一個 final 而在任何使用到了 log 的地方內聯它。只要留意基本類型和字面 String 就行了。
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。
原文鏈接:https://unmi.cc/java-reflection-modify-final-field/