Java的反射機制

来源:https://www.cnblogs.com/feiyu2/archive/2023/05/05/17375039.html
-Advertisement-
Play Games

Java 的反射機制允許在程式運行期間,藉助反射 API 獲取類的內部信息,並能直接操作對象的內部屬性及方法。 ...


介紹反射機制

Java 的反射機制允許在程式運行期間,藉助反射 API 獲取類的內部信息,並能直接操作對象的內部屬性及方法。


Java 反射機制提供的功能:

  • 在運行時,使用反射分析類的能力,獲取有關類的一切信息(類所在的包、類實現的介面、標註的註解、類的數據域、類的構造器、類的方法等)
  • 在運行時,使用反射分析對象,設置實例域的值,查看實例域的值。
  • 反射機制允許你調用任意方法(類的構造器方法、類的成員方法 等)

反射是一種功能強大且複雜的機制。使用反射機制的主要人員是工具構造者,而不是應用程式員。

Class 類

在程式運行期間,Java 運行時系統始終為所有的對象維護一個被稱為運行時的類型標識。這個信息跟蹤著每個對象所屬的類。虛擬機利用運行時類型信息選擇相應的方法執行。

然而,可以通過專門的 Java 類訪問這些信息。保存這些信息的類被稱為 Class。Object 類中的 getClass() 方法將會返回一個 Class 類型的實例。

如同用一個 Employee 對象表示一個特定的雇員屬性一樣,一個 Class 對象將表示一個特定類的屬性。

虛擬機為每個類型管理一個 Class 對象。因此,可以利用 == 運算符實現兩個 Class 對象比較的操作。

// 獲得 Class 對象的多種方式:
public static void main(String[] args) {
    // 方式 1
    // 如果 T 是任意的 Java 類型 (或 void 關鍵字), T.class 將代表匹配的 Class 對象。
    Class<Person> clazz1 = Person.class;

    // 方式 2
    Person person = new Person();
    Class clazz2 = person.getClass();

    // 方式 3
    try {
        Class clazz3 = Class.forName("類的路徑");
    } catch (ClassNotFoundException e) {
        throw new RuntimeException(e);
    }

    // 方式4
    // 獲取到 ClassLoader(這裡獲取到的是:AppClassLoader)
    ClassLoader classLoader = ClassLoader.getSystemClassLoader();
    try {
        Class clazz4 = classLoader.loadClass("類的路徑");
    } catch (ClassNotFoundException e) {
        throw new RuntimeException(e);
    }
}

還有一個很有用的方法:Class 類的 newlnstance(),可以用這個方法來動態地創建一個類的實例。newlnstance() 方法調用預設的構造器(沒有參數的構造器)初始化新創建的對象。如果這個類沒有預設的構造器,就會拋出一個 InstantiationException 異常。

將 Class 類的 forName() 方法與 Class 類的 newlnstance() 方法配合起來使用,可以根據存儲在字元串中的類名創建一個對象。

public static void main(String[] args) throws Exception {
    String className = "java.util.Random";
    Object object = Class.forName(className).newInstance();
}

如果需要以這種方式向希望按名稱創建的類的構造器提供參數,就不要使用上面那條語句,而必須使用 Constructor 類中的 newlnstance() 方法。

分析類的能力

在運行時,使用反射分析類的能力。

下麵簡要地介紹一下反射機制最重要的內容:檢查類的結構。在 java.lang.reflect 包中有三個類 Field、Method 和 Constructor 分別用於描述類的數據域、類的方法和類的構造器。


這三個類都有一個叫做 getName() 的方法,用來返回項目的名稱。

Field 類有一個 getType() 方法,用來返回描述數據域所屬類型的 Class 對象。

Method 類和 Constructor 類有能夠報告參數類型的方法,Method 類還有一個可以報告返回類型的方法。

這三個類還有一個叫做 getModifiers() 的方法,它將返回一個整型數值,用不同的位開關描述 public 和 static 這樣的修飾符使用狀況。另外, 還可以利用 java.lang.reflect 包中的 Modifier 類的靜態方法分析 getModifiers() 返回的整型數值。例如,可以使用 Modifier 類中的 isPublic()、isPrivate() 或 isFinal() 判斷方法或構造器是否是 public、private 或 final 的。我們需要做的全部工作就是調用 Modifier 類的相應方法,並對返回的整型數值進行分析,另外,還可以利用 Modifier.toString() 方法將修飾符列印出來。


Class 類的 getFields()、getMethods() 和 getConstructors() 方法將分別返回類中聲明的 public 域、public 方法和 public 構造器數組,其中包括父類的公有成員。

Class 類的 getDeclareFields()、getDeclareMethods() 和 getDeclaredConstructors() 方法將分別返回類中聲明的全部的數據域、全部的方法和全部的構造器,其中包括私有和受保護成員,但不包括父類的成員。

分析對象

在運行時,使用反射分析對象。

從前面一節中,已經知道如何查看任意對象的數據域的名稱和類型:

  • 獲得對應的 Class 對象。
  • 調用 Class 對象的 getDeclaredFields() 方法。

本節將進一步查看數據域的實際內容。當然,在編寫程式時,如果知道想要査看的數據域的名稱和類型,查看指定的數據域是一件很容易的事情。而利用反射機制可以查看在編譯時還不清楚的數據域。

查看數據域值的關鍵方法是 Field 類中的 get() 方法。如果 f 是一個 Field 類型的對象(例如,通過 getDeclaredFields() 得到的對象),obj 是某個包含 f 域的類的對象,f.get(obj) 將返回一個對象,其值為 obj 對象的 f 域的當前值。

當然,可以獲得就可以設置。調用 f.set(obj, value) 可以將 obj 對象的 f 域設置成新值。


public static void main(String[] args) {
    Employee harry = new Employee("Harry Hacker", 35000, 10, 1, 1989);
    Class cl = harry.getClass();
    
    // the class object representing Employee
    Field f = cl.getDeclaredField("name");
    // the name field of the Employee class
    Object v = f.get(harry);
    // the value of the name field of the harry object, i .e., the String object "Harry Hacker"
}

實際上,上面這段代碼存在一個問題。由於 name 是一個私有域,所以 get() 方法將會拋出一個 illegalAccessException。只有利用 get() 方法才能得到可訪問域的值。除非擁有訪問許可權,否則 Java 安全機制只允許査看任意對象有哪些域,而不允許讀取它們的值。

反射機制的預設行為受限於 Java 的訪問控制。然而,如果一個 Java 程式沒有受到安全管理器的控制,就可以覆蓋訪問控制。為了達到這個目的,需要調用 Field、Method 或 Constructor 對象的 setAccessible() 方法。例如:

f.setAtcessible(true); // now OK to call f.get(harry);

setAccessible() 方法是 AccessibleObject 類中的一個方法,AccessibleObject 類是 Field、Method 和 Constructor 類的公共父類。這個特性是為調試、持久存儲和相似機制提供的。

調用任意方法

在 C 和 C++ 中,可以從函數指針執行任意函數。從錶面上看,Java 沒有提供方法指針,即將一個方法的存儲地址傳給另外一個方法,以便第二個方法能夠隨後調用它。事實上,Java 的設計者曾說過:方法指針是很危險的,並且常常會帶來隱患。他們認為 Java 提供的介面(interface)是一種更好的解決方案。然而,反射機制允許你調用任意方法。

為了能夠看到方法指針的工作過程,先回憶一下利用 Field 類的 get() 方法查看數據域值的過程。與之類似,在 Method 類中有一個 invoke() 方法,它允許調用包裝在當前 Method 對象中的方法。


可以使用 method 對象實現 C 語言中函數指針(或 C# 中的委派)的所有操作。同 C 一樣,這種程式設計風格並不太簡便,出錯的可能性也比較大。如果在調用方法的時候提供了一個錯誤的參數,那麼 invoke() 方法將會拋出一個異常。

另外, invoke() 方法的參數和返回值必須是 Object 類型的。這就意味著必須進行多次的類型轉換。這樣做將會使編譯器錯過檢查代碼的機會。因此,等到測試階段才會發現這些錯誤,找到並改正它們將會更加困難。

在進行類型轉換的過程中,編譯器無法檢查代碼中類型轉換的正確性,也就是無法保證轉換後的類型與原始類型是相容的。這樣就會增加程式出錯的可能性,並且如果出現錯誤的話,調試和修正也會更加困難。

不僅如此,使用反射獲得方法指針的代碼執行要比直接調用方法明顯慢一些。

有鑒於此,建議僅在必要的時候才使用 Method 對象,而最好使用介面以及 Java8 中的 lambda 表達式。

特別要重申:建議 Java 開發者不要使用 Method 對象的回調功能。使用介面進行回調會使得代碼的執行速度更快,更易於維護。

參考資料

《Java核心技術捲一:基礎知識》(第10版)第 5 章:繼承 5.7 反射

本文來自博客園,作者:真正的飛魚,轉載請註明原文鏈接:https://www.cnblogs.com/feiyu2/p/17375039.html


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

-Advertisement-
Play Games
更多相關文章
  • 作為開發it行業一員,學習借鑒他人項目是很有必要的,所以我們一般都會從github或者 Gitee 上面去參考借鑒他人的項目來學習增加自己的項目經驗 但是github你真的用對了嘛,他的功能其實很強大!!! githu項目搜索 關鍵字搜索 在Github搜索欄中輸入與您感興趣的技術相關的關鍵詞,例如 ...
  • (MyBatis 配置詳解) mybatis-config.xml 核心配置文件 mybatis-config.xml 包含的內容如下 configuration(配置) properties(屬性) settings(設置) typeAliases(類型別名) typeHandlers(類型處理器 ...
  • ##(1) vector:將元素置於一個動態數組中,可以隨機存儲元素(也就是用索引直接存取)。 數組尾部添加或刪除元素非常迅速。但在中部或頭部就比較費時。 *代碼演示:* 取: at在下標越界時會拋出異常,我們能捕獲異常進行處理;而[]下標越界會讓程式直接終止; 構造函數: cbegin, cend ...
  • ​ C/C++編譯器的預設位元組對齊方式為自然對界。即在預設情況下,編譯器為每一個變數或是數據單元按其自然對界條件分配空間。 在結構中,編譯器為結構的每個成員按其自然對界(alignment)條件分配空間。各個成員按照它們被聲明的順序在記憶體中順序存儲(成員之間可能有插入的空位元組),第一個成員的地址和整 ...
  • Liquibase 和 Flyway 是兩款成熟的、優秀的、開源/商業版的資料庫版本管理工具,鑒於 Flyway 的社區版本對 Oracle 資料庫支持存在限制,所以 boot-admin 選擇整合 Liquibase 提供資料庫版本管理能力支持。 Liquibase 開源版使用 Apache 2. ...
  • 本次製作的集成安裝包集成了NSIS官方wiki認證的所有插件(無法下載或者嚴重過時的除外),翻譯了部分比較冷門插件的使用說明。額外集成了關於皮膚,按鈕美化以及用於視窗子類化等少數幾個優秀的第三方插件。所有插件皆為當前能得到的最新版本。 由於相容性原因,在不幹擾正常使用的情況下強烈建議將nsis升級為 ...
  • 項目背景 隨著互聯網和電子商務的快速發展,開發一個電影院訂票系統來幫助電影院對電影信息,售票信息進行統一化的信息管理; 遇到的問題 在設計的過程中,需要解決以下的幾個問題: 電影院會有多個播放廳,從而在同一時間播放不同的電影來滿足客戶需求 每個廳的大小可能不同,即容納的人數不同 電影院會不斷引進新片 ...
  • 新奧賽一本通,題1105 1105:數組逆序重存放 時間限制: 1000 ms 記憶體限制: 65536 KB 提交數: 70600 通過數: 47540 【題目描述】 將一個數組中的值按逆序重新存放。例如,原來的順序為8,6,5,4,1。要求改為1,4,5,6,8。 【輸入】 兩行:第一行數組中元素 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...