當final修飾一個數據域時,意義是聲明該數據域是最終的,不可修改的。常見的使用場景就是eclipse自動生成的serialVersionUID一般都是final的。
另外還可以構造線程安全(thread safe)的immutable類,比如String,其數據域都是final的。這些使用場景都建立在final不可修改這個條件上,但是,反射可以打破這一切。
1.利用反射修改final數據域
首先,構造一個Person類,里面有個final字段NAME。我們嘗試着修改這個字段。順利的出乎意料。
public class Person { public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { Person p = new Person(); Field field = p.getClass().getDeclaredField("NAME"); field.setAccessible(true); field.set(p,"Hello"); System.out.println(field.get(p)); //p.printName(); } private final String NAME = "Clive"; public Person() { } public void printName() { System.out.println(NAME); } } /*************** console print: Hello ***************/
2.內聯與內聯消除
NAME數據域如此簡單的就被修改了,final真是太"不安全了"! 但是,當我們調用p.printName() 時,控制台打印的卻是"Clive"字符串。這是因為JVM做了優化處理, 當一個數據域被final修飾,那就表明這個數據域是常量,JVM會把所有NAME數據域出現的地方全部用"Clive"替換掉, 比如 printName() 方法其實被優化成了這樣。
public void printName() { System.out.println("Clive"); }
所以,要想不被自動優化,就要把代碼弄得復雜點,如下
public class Person { public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { Person p = new Person(); Field field = p.getClass().getDeclaredField("NAME"); field.setAccessible(true); field.set(p,"Hello"); System.out.println(field.get(p)); p.printName(); } private final String NAME =(null!=null?"Clive":"Clive"); //聲明時即初始化 public Person() { //或者,在這里設置NAME數據域的值 //NAME="Clive"; } public void printName() { System.out.println(NAME); } } /*************** console print: Hello Hello ***************/
結果見 console print,順利消除了優化,final字段最終被修改了!
3.修改static final數據域
如果在NAME字段再增加一個static關鍵字修飾,然后再用反射修改的話就不行了, 會拋出異常
java.lang.IllegalAccessException: Can not set static final int field ...
這時,修改Field中的modifiers數據域,清除代表final的那個bit,才可以成功修改。
public class Person { public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { Person p = new Person(); Field field = p.getClass().getDeclaredField("NAME"); Field modifiers = field.getClass().getDeclaredField("modifiers"); modifiers.setAccessible(true); modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);//fianl標志位置0 field.set(p,"Hello"); System.out.println(field.get(p)); p.printName(); } private final String NAME =(null!=null?"Clive":"Clive"); public Person() { } public void printName() { System.out.println(NAME); } } /************** console print: Hello Hello **************/
總結
這個知識點感覺知道就好,平時還是不要修改final數據域的好 :)