Java?MethodHandles介紹與反射對比區(qū)別詳解
前言
在本文中,我們將探討一個重要的API,它是在Java7中引入的,并在以后的jdk版本中得到了增強,即java.lang.invoke.MethodHandles
。
特別是,我們將學習什么是方法句柄(method handles),如何創(chuàng)建它們以及如何使用它們。
什么是方法句柄?
如API文件中所述,關于其定義:
方法句柄是對基礎方法、構造函數(shù)、字段或類似低級操作的類型化、直接可執(zhí)行的引用,具有參數(shù)或返回值的可選轉換。
更簡單地說,方法句柄是一種用于查找、調整和調用方法的低級機制。
方法句柄是不可變的,并且沒有可見的狀態(tài)。
要創(chuàng)建和使用MethodHandle,需要4個步驟:
- 創(chuàng)建lookup
- 創(chuàng)建method type
- 查找方法句柄
- 調用方法句柄
方法句柄與反射
引入方法句柄是為了與現(xiàn)有的java.lang.reflect
API一起工作,因為它們具有不同的用途和不同的特性。
從性能角度來看,MethodHandles API可能比Reflection API快得多,因為訪問檢查是在創(chuàng)建時而不是在執(zhí)行時進行的。如果存在安全管理器,則這種差異會被放大,因為成員和類查找要接受額外的檢查。
然而,考慮到性能并不是任務的唯一適用性度量,我們還必須考慮到,由于缺乏成員類枚舉、可訪問性標志檢查等機制,MethodHandles API更難使用。
即便如此,MethodHandles API提供了柯里化方法、更改參數(shù)類型和更改其順序的可能性。
有了MethodHandles API的清晰定義和目標,我們現(xiàn)在可以從lookup
開始使用它們。
創(chuàng)建Lookup
當我們想要創(chuàng)建方法句柄時,要做的第一件事是檢索查找Lookup,即負責為查找類可見的方法、構造函數(shù)和字段創(chuàng)建方法句柄的工廠對象。
通過MethodHandles API,可以創(chuàng)建具有不同訪問模式的查找對象。
讓我們創(chuàng)建一個提供對公共方法訪問的查找:
MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();
然而,如果我們也想訪問私有和受保護的方法,我們可以使用lookup()
方法:
MethodHandles.Lookup lookup = MethodHandles.lookup();
創(chuàng)建MethodType
為了能夠創(chuàng)建MethodHandle,查找對象需要其類型的定義,這是通過MethodType類實現(xiàn)的。
特別是,MethodType表示方法句柄接受和返回的參數(shù)和返回類型,或方法句柄調用程序傳遞和期望的參數(shù)和返回類型。
MethodType的結構很簡單,它由一個返回類型和適當數(shù)量的參數(shù)類型組成,這些參數(shù)類型必須在方法句柄及其所有調用方之間正確匹配。
與MethodHandle相同,即使是MethodType的實例也是不可變的。
讓我們看看如何定義一個MethodType,該MethodType將java.util.List
類指定為返回類型,將Object
數(shù)組指定為輸入類型:
MethodType mt = MethodType.methodType(List.class, Object[].class);
如果該方法返回基本類型或void
作為其返回類型,我們將使用表示這些類型的類(void.class、int.class…)
。
讓我們定義一個返回int
值并接受Object
的MethodType:
MethodType mt = MethodType.methodType(int.class, Object.class);
我們現(xiàn)在可以繼續(xù)創(chuàng)建MethodHandle。
找到方法句柄
一旦我們定義了方法類型,為了創(chuàng)建MethodHandle,我們必須通過lookup
或publicLookup
對象找到它,同時提供原始類和方法名稱。
特別是,查找工廠提供了一組方法,使我們能夠在考慮方法范圍的情況下以適當?shù)姆绞秸业椒椒ň浔?。從最簡單的場景開始,讓我們探究主要的場景。
方法的MethodHandle
使用findVirtual()
方法可以為對象方法創(chuàng)建一個MethodHandle。讓我們根據(jù)String類的concat()
方法創(chuàng)建一個:
MethodType mt = MethodType.methodType(String.class, String.class); MethodHandle concatMH = publicLookup.findVirtual(String.class, "concat", mt);
靜態(tài)方法的方法句柄
當我們想要訪問靜態(tài)方法時,我們可以使用findStatic()
方法:
MethodType mt = MethodType.methodType(List.class, Object[].class); MethodHandle asListMH = publicLookup.findStatic(Arrays.class, "asList", mt);
在本例中,我們創(chuàng)建了一個方法句柄,用于將對象數(shù)組轉換為對象列表。
構造函數(shù)的方法句柄
可以使用findConstructor()
方法訪問構造函數(shù)。
讓我們創(chuàng)建一個方法句柄,它充當Integer
類的構造函數(shù),接受String
屬性:
MethodType mt = MethodType.methodType(void.class, String.class); MethodHandle newIntegerMH = publicLookup.findConstructor(Integer.class, mt);
字段的方法句柄
使用方法句柄也可以訪問字段。
讓我們開始定義Book
類:
public class Book { String id; String title; // constructor }
先決條件是方法句柄和聲明的屬性之間具有直接訪問可見性,我們可以創(chuàng)建一個充當getter
的方法句柄:
MethodHandle getTitleMH = lookup.findGetter(Book.class, "title", String.class);
有關處理變量/字段的更多信息,請參閱Java 9 Variable Handles:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/...
私有方法的方法句柄
在java.lang.reflect
API的幫助下,可以為私有方法創(chuàng)建方法句柄。
讓我們開始向Book
類添加一個私有方法:
private String formatBook() { return id + " > " + title; }
現(xiàn)在,我們可以創(chuàng)建一個與formatBook()
方法完全相同的方法句柄:
Method formatBookMethod = Book.class.getDeclaredMethod("formatBook"); formatBookMethod.setAccessible(true); MethodHandle formatBookMH = lookup.unreflect(formatBookMethod);
調用方法句柄
一旦我們創(chuàng)建了方法句柄,下一步就是使用它們。特別是,MethodHandle類提供了3種不同的方法來執(zhí)行方法句柄:invoke()
、invokeWithAruments()
和invokeExact()
。
讓我們從invoke
選項開始。
當使用invoke()
方法時,我們強制要固定的參數(shù)數(shù)量,但我們允許執(zhí)行參數(shù)和返回類型的強制轉換和裝箱/取拆箱。
讓我們看看如何使用帶框參數(shù)的invoke()
:
MethodType mt = MethodType.methodType(String.class, char.class, char.class); MethodHandle replaceMH = publicLookup.findVirtual(String.class, "replace", mt); String output = (String) replaceMH.invoke("jovo", Character.valueOf('o'), 'a'); assertEquals("java", output);
在這種情況下,replaceMH
需要char參數(shù),但invoke()
在執(zhí)行之前會對Character
參數(shù)執(zhí)行開箱操作。
使用參數(shù)調用
使用invokeWithArguments
方法調用方法句柄是三個選項中限制最小的一個。
事實上,除了參數(shù)和返回類型的強制轉換和裝箱/取消裝箱外,它還允許變量arity調用。
在實踐中,這允許我們從一個int
值數(shù)組開始創(chuàng)建一個Integer
列表:
MethodType mt = MethodType.methodType(List.class, Object[].class); MethodHandle asList = publicLookup.findStatic(Arrays.class, "asList", mt); List<Integer> list = (List<Integer>) asList.invokeWithArguments(1,2); assertThat(Arrays.asList(1,2), is(list));
調用Exact
如果我們想在執(zhí)行方法句柄的方式上更加嚴格(參數(shù)的數(shù)量及其類型),我們必須使用invokeExact()
方法。
事實上,它沒有為所提供的類提供任何類型轉換,并且需要固定數(shù)量的參數(shù)。
讓我們看看如何使用方法句柄對兩個int
值求和:
MethodType mt = MethodType.methodType(int.class, int.class, int.class); MethodHandle sumMH = lookup.findStatic(Integer.class, "sum", mt); int sum = (int) sumMH.invokeExact(1, 11); assertEquals(12, sum);
如果在這種情況下,我們決定向invokeExact
方法傳遞一個不是int
的數(shù)字,那么調用將導致WrongMethodTypeException
。
使用數(shù)組
MethodHandles不僅用于字段或對象,還用于數(shù)組。事實上,使用asSpreader()
API,可以生成一個數(shù)組擴展方法句柄。
在這種情況下,方法句柄接受一個數(shù)組參數(shù),將其元素擴展為位置參數(shù),并可以選擇數(shù)組的長度。
讓我們看看如何擴展方法句柄來檢查數(shù)組中的元素是否相等:
MethodType mt = MethodType.methodType(boolean.class, Object.class); MethodHandle equals = publicLookup.findVirtual(String.class, "equals", mt); MethodHandle methodHandle = equals.asSpreader(Object[].class, 2); assertTrue((boolean) methodHandle.invoke(new Object[] { "java", "java" }));
增強方法句柄
一旦我們定義了一個方法句柄,就可以通過將方法句柄綁定到一個參數(shù)來增強它,而無需實際調用它。
例如,在Java9中,這種行為用于優(yōu)化字符串連接。
讓我們看看如何執(zhí)行串聯(lián),將后綴綁定到我們的concatMH
:
MethodType mt = MethodType.methodType(String.class, String.class); MethodHandle concatMH = publicLookup.findVirtual(String.class, "concat", mt); MethodHandle bindedConcatMH = concatMH.bindTo("Hello "); assertEquals("Hello World!", bindedConcatMH.invoke("World!"));
Java 9增強功能
在Java9中,對MethodHandles API進行了一些增強,目的是使其更易于使用。
這些增強影響了3個主要主題:
- 查找函數(shù)–允許從不同上下文中查找類,并支持接口中的非抽象方法
- 參數(shù)處理——改進參數(shù)折疊、參數(shù)收集和參數(shù)傳播功能
- 附加組合–添加循環(huán)(
loop
、whileLoop
、doWhileLoop
…),并通過tryFinally
提供更好的異常處理支持
這些變化帶來了一些額外的好處:
- 增加JVM編譯器優(yōu)化
- 實例化減少
- 在使用MethodHandles API時啟用了精度
結論
在本文中,我們介紹了MethodHandles API、它們是什么以及如何使用它們。
我們還討論了它與反射API的關系,由于方法句柄允許低級別操作,因此最好避免使用它們,除非它們完全適合工作范圍。
以上就是Java MethodHandles介紹與反射對比區(qū)別詳解的詳細內容,更多關于Java MethodHandles對比反射的資料請關注腳本之家其它相關文章!
相關文章
使用Postman自動生成Cookie并轉換為Java代碼的實現(xiàn)
在接口測試中,有時候需要在請求中攜帶Cookie信息,為了方便測試,我們可以使用Postman來自動生成Cookie,并將其轉換為Java代碼,以便在自動化測試中使用,下面將介紹如何實現(xiàn)這一功能,需要的朋友可以參考下2024-11-11解決出現(xiàn) java.lang.ExceptionInInitializerError錯誤問題
這篇文章主要介紹了解決出現(xiàn) java.lang.ExceptionInInitializerError錯誤問題的相關資料,需要的朋友可以參考下2017-01-01詳解獲取Spring MVC中所有RequestMapping以及對應方法和參數(shù)
本篇文章主要介紹了詳解獲取Spring MVC中所有RequestMapping以及對應方法和參數(shù),具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-03-03