當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標志位置0field.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數據域的好 :)
引用
1.https://www.oschina.net/question/1245392_159103
2.https://github.com/jOOQ/jOOR