Java反射-修改字段值, 反射修改static final修飾的字段


反射修改字段

咱們從最簡單的例子到難, 一步一步深入. 

使用反射修改一個private修飾符的變量name

咱們回到主題, 先用反射來實現一個最基礎的功能吧.

其中待獲取的name如下:

public class Pojo {
    private StringBuilder name = new StringBuilder("default");

    public void printName() {
        System.out.println(name);
    }
}

接下來咱們 使用反射來修改上面name的值.

為什么要用反射呢? 因為成員變量name是private修飾的, 而且沒有提供一個setter方法.沒有方法可以設置name的值.

雖然沒有一個對外開放的接口, 但是反射卻可以輕而易舉地做到:

        Pojo p = new Pojo();

        // 查看被修改之前的值
        p.printName();

        // 反射獲取字段, name成員變量
        Field nameField = p.getClass().getDeclaredField("name");

        // 由於name成員變量是private, 所以需要進行訪問權限設定
        nameField.setAccessible(true);

        // 使用反射進行賦值
        nameField.set(p, new StringBuilder("111"));

        // 打印查看被修改后的值
        p.printName();

 發現被修改成功, 結果如下:

使用反射修改一個final修飾符的變量name

剛才使用反射成功修改了private修飾的變量, 那么如果是final修飾的變量那么還能否使用反射來進行修改呢? (因為正常的setter getter操作反正是做不到.)

聲明一個final修飾的name如下. 接下來使用反射來對它進行修改. 目的也就是使name指向一個新的StringBuilder對象.

public class Pojo2 {
    private final StringBuilder name = new StringBuilder("default2");

    public void printName() {
        System.out.println(name);
    }
}

  咱們看看反射的威力吧, 它能修改final的字段的指向.也就是讓name字段指向一個新的地址.

        Pojo2 p = new Pojo2();

        // 查看被修改之前的值
        p.printName();

        // 反射獲取字段, name成員變量
        Field nameField = p.getClass().getDeclaredField("name");

        // 由於name成員變量是private, 所以需要進行訪問權限設定
        nameField.setAccessible(true);

        // 使用反射進行賦值
        nameField.set(p, new StringBuilder("111"));
        
        // 打印查看被修改后的值
        p.printName();

 發現設置成功, 結果如下:

使用反射修改一個final修飾符的String類型變量name

如果說同學們在看我這篇文章時, 在前面偷懶了, 或者是認為StringBuilder和String沒什么大區別, 於是就在前面把我代碼里的StringBuilder都改為了String, 那么大家的執行結果將會是一個意外結果.

也就是我前面的例子用StringBuilder就能成功, 如果都替換成了String, 使用反射也不能夠成功賦值.

為什么呢?

在講解為什么之前, 我這里把這個問題重現一下:

把前面的StringBuilder替換為String后的Pojo3

public class Pojo3 {
    private final String name = "default3";

    public void printName() {
        System.out.println(name);
    }
}

使用反射嘗試着進行賦值:

        Pojo3 p = new Pojo3();
        p.printName();
        Field nameField = p.getClass().getDeclaredField("name");
        nameField.setAccessible(true);
        nameField.set(p, "111");
        p.printName();

 發現賦值失敗, 結果如下:


再一次提問: 為什么呢?

因為JVM在編譯時期, 就把final類型的String進行了優化, 在編譯時期就會把String處理成常量, 所以 Pojo3里的printName()方法, 就相當於:

public void printName() {
        System.out.println("default3");
    }

 其實name的值是賦值成功了, 只是printName()方法在JVM優化后就被寫死了, 所以無論name是否被正確修改為其他的值, printName始終都會打印"default3".

那么怎么知道name是不是真的被重新賦值成功了呢?

看下面代碼:

        Pojo3 p = new Pojo3();
        Field nameField = p.getClass().getDeclaredField("name");
        nameField.setAccessible(true);
        
        // 使用反射向name進行重新賦值
        nameField.set(p, "111");
        
        // 再使用反射再把name值取出來
        Object name = nameField.get(p);
        
        // 把取出來的name值進行打印
        System.out.println(name.toString());

 結果如下, 說明name變量確實被賦值成功.  


那么可能有同學就問了,final修飾的String在JVM編譯時就被處理為常量,  怎么樣防止這種現象呢?   請看下面講解

使用反射修改一個final修飾符的String類型變量name, 同時防止字符串在編譯時被處理為常量

使用一些手段讓final String類型的name的初始值經過一次運行才能得到, 那么就不會在編譯時期就被處理為常亮了.

public class Pojo4 {
    // 防止JVM編譯時就把"default4"作為常量處理
    private final String name = (null == null ? "default4" : "");

    public void printName() {
        System.out.println(name);
    }
}

  運行測試的還是那段反射代碼:

        Pojo4 p = new Pojo4();
        p.printName();
        Field nameField = p.getClass().getDeclaredField("name");
        nameField.setAccessible(true);
        nameField.set(p, "111");
        p.printName();

 結果如下, 發現確實成功了:  


那么有同學就會問了, 除了上面這種方法外, 還有什么方法能防止JVM在編譯時就把final String的變量處理為常亮呢 ?

答: 嗯...只要是讓name的值經過運行才能獲得, 那么就不會被處理為常量. 我再舉個程序例子吧.看下面代碼:

public class Pojo5 {
    private final String name = new StringBuilder("default5").toString();

    public void printName() {
        System.out.println(name);
    }
}

  還是那段反射的代碼, 運行

    @Test
    public void test5() throws Exception {
        Pojo5 p = new Pojo5();
        p.printName();
        Field nameField = p.getClass().getDeclaredField("name");
        nameField.setAccessible(true);
        nameField.set(p, "111");
        p.printName();
    }

 結果如下, OK

使用反射修改一個static修飾符的變量name

剛才展示了使用反射來修改final修飾的字段, 接下來就演示一下使用反射來修改static修飾的變量:

如下的一個static修飾的一個name變量.

public class Pojo6 {
    private static StringBuilder name = new StringBuilder("default6");

    public void printName() {
        System.out.println(name);
    }
}

還是那段反射代碼來進行測試:

        Pojo6 p = new Pojo6();
        p.printName();
        Field nameField = p.getClass().getDeclaredField("name");
        nameField.setAccessible(true);
        nameField.set(p, new StringBuilder("111"));
        p.printName();

 發現結果如下, 也可以設置成功.  

使用反射修改final + static修飾符的變量name

一個同時被final和static修飾的變量如下所示:

public class Pojo7 {
    private final static StringBuilder name = new StringBuilder("default7");

    public void printName() {
        System.out.println(name);
    }
}

  如果還是通過下面這段反射代碼來進行修改name的值, 那么就錯了!

        Pojo7 p = new Pojo7();
        p.printName();
        Field nameField = p.getClass().getDeclaredField("name");
        nameField.setAccessible(true);
        nameField.set(p, new StringBuilder("111"));
        p.printName();

 執行之后會報出如下異常, 因為反射無法修改同時被static final修飾的變量:

 那到底能不能修改呢?

 答案是能修改.

那怎么樣修改呢?

思路是這樣的, 先通過反射把name字段的final修飾符去掉.看如下代碼:

先把name字段通過反射取出來, 這個和之前的步驟都一樣, 反射出來的字段類型(Field)命名為'nameField'

        Field nameField = p.getClass().getDeclaredField("name");
        nameField.setAccessible(true);

 接下來再通過反射, 把nameField的final修飾符去掉:

        Field modifiers = nameField.getClass().getDeclaredField("modifiers");
        modifiers.setAccessible(true);
        modifiers.setInt(nameField, nameField.getModifiers() & ~Modifier.FINAL);

 然后就可以正常對name字段進行值的修改了.

        nameField.set(p, new StringBuilder("111"));

 最后別忘了再把final修飾符加回來:

modifiers.setInt(nameField, nameField.getModifiers() & ~Modifier.FINAL);

 本例子中反射部分完整的代碼如下:

        // 注釋的這段代碼這樣使用是錯誤的
//        Pojo7 p = new Pojo7();
//        p.printName();
//        Field nameField = p.getClass().getDeclaredField("name");
//        nameField.setAccessible(true);
//        nameField.set(p, new StringBuilder("111"));
//        p.printName();

        Pojo7 p = new Pojo7();
        p.printName();
        Field nameField = p.getClass().getDeclaredField("name");
        nameField.setAccessible(true);


        Field modifiers = nameField.getClass().getDeclaredField("modifiers");
        modifiers.setAccessible(true);
        modifiers.setInt(nameField, nameField.getModifiers() & ~Modifier.FINAL);

        nameField.set(p, new StringBuilder("111"));
        modifiers.setInt(nameField, nameField.getModifiers() & ~Modifier.FINAL);
        p.printName();

 結果如下, 表示修改正確:  

 

本文中的所有代碼都在這里: https://github.com/GoldArowana/K-Object/tree/master/src/test/java/reflect/field   里面的TestField.java為主要反射代碼


免責聲明!

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



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