利用反射修改final數據域


當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數據域的好 :)

引用

1.https://www.oschina.net/question/1245392_159103

2.https://github.com/jOOQ/jOOR


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM