Java反射-修改欄位值, 反射修改static final修飾的欄位

来源:https://www.cnblogs.com/noKing/archive/2018/05/15/9038234.html
-Advertisement-
Play Games

Java反射, 修改欄位值, 實例化對象, 繞過構造器來實例化對象 ...


反射修改欄位

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

使用反射修改一個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為主要反射代碼


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 最近不知道寫什麼,node的源碼有點不知道怎麼入手,所以還在努力學習中…… 在寫微信公眾號的時候遇到了一個小bug,有一個tab欄,在開發者工具、IOS手機上都OK,但是一到我的小米note上就GG了,怎麼切換都無法觸發對應的請求,核心代碼如下: 點擊切換後,會重置分頁的index參數,然後將滾輪弄 ...
  • 前面的話 隨著移動互聯網的興起,不同設備的解析度相差較大,如果在不同的設置上顯示同一個頁面,則用戶體驗差。響應式網頁設計是一種方法,使得一個網站能夠相容多個終端,而不用為每個終端製作特定的版本。它使得一個網站可以在任何類型的屏幕上,都可以被輕鬆地瀏覽和使用。採用響應式設計,在不同設備中,網站會重新排 ...
  • 微信授權、獲取用戶openid-純前端實現——jsonp跨域訪問返回json數據會報錯的純前端解決辦法 ...
  • 1、事件:addEventListener('click', function(){}或者方法名字) 可以添加多個處理器,語法: 對應的有removeEventListener()。 2、阻止表單預設行為: preventDefault();當在事件對象上調用該函數 3、阻止事件冒泡: stopPr ...
  • 閱讀目錄: 1. ELK Stack 簡介 2. 環境準備 3. 安裝 Elasticsearch 4. 安裝 Kibana 5. Kibana 使用 6. Elasticsearch 命令 最近在開發分散式服務追蹤,使用 Spring Cloud Sleuth Zipkin + Stream + ...
  • 在整理模板方法之前,先來說點廢話吧。除了記錄學習總結,也來記錄一下生活吧。 我們公司的老闆在北京,老闆也會因為項目來公司,不過不是天天來。公司有個同事,只要老闆不在就天天遲到,而且一天比一天晚,經常來了公司沒多久,午飯的外賣就送到公司了。前幾天,外賣竟然比他還來的早(外賣11點半送到的),公司一個不 ...
  • 一、什麼是裝飾模式 裝飾模式(Decorator),動態地給一個對象添加一些額外的職責,就增加功能來說,裝飾模式比生成子類更靈活。UML結構圖如下: 其中,Component是抽象構件,定義一個對象介面,可以給這些對象動態地添加職責;ConreteComponent定義一個具體對象,也可以給這個對象 ...
  • 剛畫出來的,裡邊配的騷詞出自本人,夠騷吧! ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...