反射API
Java是一種具有反射功能的語言。允許開發人員在運作時檢查類型、方法、字段、注解等,并在程式運作時決定是否使用。 為此,Java的反射API提供類,類,字段,構造函數,方法,注釋和其他。 使用它們可以與編譯時未知的類型進行互動,例如建立未知類的執行個體并對它們調用方法。
這個快速提示旨在讓您深度了解什麼是反射,它在Java中的使用,以及它可以用于什麼。 之後,你将準備好開始或工作更長的教程。 為了充分使用它,你應該很好地了解Java的類構造器,特别是什麼類和方法以及它們如何關聯。 了解注釋可解鎖單獨的部分。
舉個栗子
我們從一個簡單的Java代碼開始(代碼針對有一定java基礎的閱讀人員)
URL url = new URL("https://sitepoint.com/java");
String urlString = url.toExternalForm();
System.out.println(urlString);
我決定在編譯時(即當我在寫代碼時)建立一個
URL對象,并且調用了其中的一些方法。下面示範了我使用Java的反射API完成了同樣的事情:
// the gateway to reflection is the `Class` instance
// for the class you want to operate on
Class<?> type = Class.forName("java.net.URL");
// fetches the constructor that takes a `String` argument
// and uses it to create a new instance for the given string
Constructor<?> constructor = type.getConstructor(String.class);
Object instance = constructor.newInstance("https://sitepoint.com/java");
// fetches the `toExternalForm` method and invokes it on
// the instance that was just created
Method method = type.getMethod("toExternalForm");
Object methodCallResult = method.invoke(instance);
System.out.println(methodCallResult);
使用反射API的确比直接寫代碼要笨重一點. 但是使用反射你會發現, 你在代碼當中的調用細節 (比如說我用的URL這個類以及我調用其中的方法) 變成了僅僅一個參數. 結果呢,在編譯期間URL以及toExternalForm并沒有綁定, 它們是在程式開始運作的時候才被決定綁定的
反射的大多數用例都是“架構”的場景下 ,想想junit, 比如說, 執行所有被@ test注解的方法. 一旦架構在classpath掃描的時候找到注解它就會調用getMethod以及invoke函數去執行使用者代碼. spring和其他的一些web架構在搜尋控制器以及url映射的收也差不多是這麼做的,對可擴充的應用程式來說反射的另外一個用途就是在運作時加載使用者提供的插件
基本類型和方法
調用反射API的方法是Class :: forName。 在它的簡單形式中,這個靜态方法隻需要一個完全限定的類名,并為它傳回一個Class執行個體。 該執行個體可用于擷取字段,方法,構造函數等。
通過構造器函數擷取類對象,getConstructor方法可以使用構造函數參數的類型調用,就像我上面做的那樣。 類似地,可以通過調用getMethod并傳遞其名稱以及參數類型來通路特定方法。 上面的getMethod(“toExternalForm”)調用沒有指定任何類型,因為該方法沒有參數。
這裡有一個方法:
Class<?> type = Class.forName("java.net.URL");
// `URL::openConnection` has an overload that accepts a java.net.Proxy
Method openConnection = type.getMethod("openConnection", Proxy.class);
這些調用傳回的執行個體分别是Constructor和Method類型。 要調用底層成員,他們提供類似于Constructor :: newInstance和Method :: invoke的方法。 後者的一個有趣的細節是,要調用該方法的執行個體需要作為第一個參數傳遞給它(它指類的執行個體)。 其他參數将被傳遞給被調用的方法。
繼續openConnection示例:
openConnection.invoke(instance, someProxy);
如果要調用靜态方法,則将忽略執行個體參數,是以可以為null。
注解
注解是反射的重要組成部分。 事實上,注解主要針對反射。 它們旨在提供程式運作時通路的元資訊,然後用于塑造程式的行為。 (如上所述,JUnit的@Test和Spring的@Controller和@RequestMapping是很好的例子。)
所有重要的反射相關類型,如類,字段,構造函數,方法和參數實作AnnotatedElement接口。 連結的Javadoc包含了注釋如何與這些元素(直接呈現,間接呈現或關聯)相關的詳細解釋,但是它最簡單的形式是:getAnnotations方法以注釋執行個體數組的形式傳回該元素上存在的注釋 ,然後可以通路其成員。
總結
Java的反射API允許在運作時自動檢查類型,方法,注釋等,并調用在編譯時未知的構造函數和方法。首先,請調用
Class.forName("fully.qualified.class.Name")
,然後調用
getConstructors
,
getMethods
,
getAnnotations
或類似方法。調用伴随構造函數的
newInstance
和方法的
invoke一起發生
。
你也可以使用反射分解代碼并更改非公共字段或調用非公共方法 - 這是一個冒險的做法,而且在Java 9中變得更加困難。如果你很好奇,并希望知道所有的API的來龍去脈,在Java的官方文檔中給出了反射線索。到目前為止,API有點過時并有一些缺點,但是新的替代方案存在,檢視方法句柄(自Java 7)和變量句柄(自Java 9)。