Java?MethodHandles介紹與反射對(duì)比區(qū)別詳解
前言
在本文中,我們將探討一個(gè)重要的API,它是在Java7中引入的,并在以后的jdk版本中得到了增強(qiáng),即java.lang.invoke.MethodHandles
。
特別是,我們將學(xué)習(xí)什么是方法句柄(method handles),如何創(chuàng)建它們以及如何使用它們。
什么是方法句柄?
如API文件中所述,關(guān)于其定義:
方法句柄是對(duì)基礎(chǔ)方法、構(gòu)造函數(shù)、字段或類(lèi)似低級(jí)操作的類(lèi)型化、直接可執(zhí)行的引用,具有參數(shù)或返回值的可選轉(zhuǎn)換。
更簡(jiǎn)單地說(shuō),方法句柄是一種用于查找、調(diào)整和調(diào)用方法的低級(jí)機(jī)制。
方法句柄是不可變的,并且沒(méi)有可見(jiàn)的狀態(tài)。
要?jiǎng)?chuàng)建和使用MethodHandle,需要4個(gè)步驟:
- 創(chuàng)建lookup
- 創(chuàng)建method type
- 查找方法句柄
- 調(diào)用方法句柄
方法句柄與反射
引入方法句柄是為了與現(xiàn)有的java.lang.reflect
API一起工作,因?yàn)樗鼈兙哂胁煌挠猛竞筒煌奶匦浴?/p>
從性能角度來(lái)看,MethodHandles API可能比Reflection API快得多,因?yàn)樵L問(wèn)檢查是在創(chuàng)建時(shí)而不是在執(zhí)行時(shí)進(jìn)行的。如果存在安全管理器,則這種差異會(huì)被放大,因?yàn)槌蓡T和類(lèi)查找要接受額外的檢查。
然而,考慮到性能并不是任務(wù)的唯一適用性度量,我們還必須考慮到,由于缺乏成員類(lèi)枚舉、可訪問(wèn)性標(biāo)志檢查等機(jī)制,MethodHandles API更難使用。
即便如此,MethodHandles API提供了柯里化方法、更改參數(shù)類(lèi)型和更改其順序的可能性。
有了MethodHandles API的清晰定義和目標(biāo),我們現(xiàn)在可以從lookup
開(kāi)始使用它們。
創(chuàng)建Lookup
當(dāng)我們想要?jiǎng)?chuàng)建方法句柄時(shí),要做的第一件事是檢索查找Lookup,即負(fù)責(zé)為查找類(lèi)可見(jiàn)的方法、構(gòu)造函數(shù)和字段創(chuàng)建方法句柄的工廠對(duì)象。
通過(guò)MethodHandles API,可以創(chuàng)建具有不同訪問(wèn)模式的查找對(duì)象。
讓我們創(chuàng)建一個(gè)提供對(duì)公共方法訪問(wèn)的查找:
MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();
然而,如果我們也想訪問(wèn)私有和受保護(hù)的方法,我們可以使用lookup()
方法:
MethodHandles.Lookup lookup = MethodHandles.lookup();
創(chuàng)建MethodType
為了能夠創(chuàng)建MethodHandle,查找對(duì)象需要其類(lèi)型的定義,這是通過(guò)MethodType類(lèi)實(shí)現(xiàn)的。
特別是,MethodType表示方法句柄接受和返回的參數(shù)和返回類(lèi)型,或方法句柄調(diào)用程序傳遞和期望的參數(shù)和返回類(lèi)型。
MethodType的結(jié)構(gòu)很簡(jiǎn)單,它由一個(gè)返回類(lèi)型和適當(dāng)數(shù)量的參數(shù)類(lèi)型組成,這些參數(shù)類(lèi)型必須在方法句柄及其所有調(diào)用方之間正確匹配。
與MethodHandle相同,即使是MethodType的實(shí)例也是不可變的。
讓我們看看如何定義一個(gè)MethodType,該MethodType將java.util.List
類(lèi)指定為返回類(lèi)型,將Object
數(shù)組指定為輸入類(lèi)型:
MethodType mt = MethodType.methodType(List.class, Object[].class);
如果該方法返回基本類(lèi)型或void
作為其返回類(lèi)型,我們將使用表示這些類(lèi)型的類(lèi)(void.class、int.class…)
。
讓我們定義一個(gè)返回int
值并接受Object
的MethodType:
MethodType mt = MethodType.methodType(int.class, Object.class);
我們現(xiàn)在可以繼續(xù)創(chuàng)建MethodHandle。
找到方法句柄
一旦我們定義了方法類(lèi)型,為了創(chuàng)建MethodHandle,我們必須通過(guò)lookup
或publicLookup
對(duì)象找到它,同時(shí)提供原始類(lèi)和方法名稱(chēng)。
特別是,查找工廠提供了一組方法,使我們能夠在考慮方法范圍的情況下以適當(dāng)?shù)姆绞秸业椒椒ň浔?。從最?jiǎn)單的場(chǎng)景開(kāi)始,讓我們探究主要的場(chǎng)景。
方法的MethodHandle
使用findVirtual()
方法可以為對(duì)象方法創(chuàng)建一個(gè)MethodHandle。讓我們根據(jù)String類(lèi)的concat()
方法創(chuàng)建一個(gè):
MethodType mt = MethodType.methodType(String.class, String.class); MethodHandle concatMH = publicLookup.findVirtual(String.class, "concat", mt);
靜態(tài)方法的方法句柄
當(dāng)我們想要訪問(wèn)靜態(tài)方法時(shí),我們可以使用findStatic()
方法:
MethodType mt = MethodType.methodType(List.class, Object[].class); MethodHandle asListMH = publicLookup.findStatic(Arrays.class, "asList", mt);
在本例中,我們創(chuàng)建了一個(gè)方法句柄,用于將對(duì)象數(shù)組轉(zhuǎn)換為對(duì)象列表。
構(gòu)造函數(shù)的方法句柄
可以使用findConstructor()
方法訪問(wèn)構(gòu)造函數(shù)。
讓我們創(chuàng)建一個(gè)方法句柄,它充當(dāng)Integer
類(lèi)的構(gòu)造函數(shù),接受String
屬性:
MethodType mt = MethodType.methodType(void.class, String.class); MethodHandle newIntegerMH = publicLookup.findConstructor(Integer.class, mt);
字段的方法句柄
使用方法句柄也可以訪問(wèn)字段。
讓我們開(kāi)始定義Book
類(lèi):
public class Book { String id; String title; // constructor }
先決條件是方法句柄和聲明的屬性之間具有直接訪問(wèn)可見(jiàn)性,我們可以創(chuàng)建一個(gè)充當(dāng)getter
的方法句柄:
MethodHandle getTitleMH = lookup.findGetter(Book.class, "title", String.class);
有關(guān)處理變量/字段的更多信息,請(qǐng)參閱Java 9 Variable Handles:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/...
私有方法的方法句柄
在java.lang.reflect
API的幫助下,可以為私有方法創(chuàng)建方法句柄。
讓我們開(kāi)始向Book
類(lèi)添加一個(gè)私有方法:
private String formatBook() { return id + " > " + title; }
現(xiàn)在,我們可以創(chuàng)建一個(gè)與formatBook()
方法完全相同的方法句柄:
Method formatBookMethod = Book.class.getDeclaredMethod("formatBook"); formatBookMethod.setAccessible(true); MethodHandle formatBookMH = lookup.unreflect(formatBookMethod);
調(diào)用方法句柄
一旦我們創(chuàng)建了方法句柄,下一步就是使用它們。特別是,MethodHandle類(lèi)提供了3種不同的方法來(lái)執(zhí)行方法句柄:invoke()
、invokeWithAruments()
和invokeExact()
。
讓我們從invoke
選項(xiàng)開(kāi)始。
當(dāng)使用invoke()
方法時(shí),我們強(qiáng)制要固定的參數(shù)數(shù)量,但我們?cè)试S執(zhí)行參數(shù)和返回類(lèi)型的強(qiáng)制轉(zhuǎn)換和裝箱/取拆箱。
讓我們看看如何使用帶框參數(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í)行之前會(huì)對(duì)Character
參數(shù)執(zhí)行開(kāi)箱操作。
使用參數(shù)調(diào)用
使用invokeWithArguments
方法調(diào)用方法句柄是三個(gè)選項(xiàng)中限制最小的一個(gè)。
事實(shí)上,除了參數(shù)和返回類(lèi)型的強(qiáng)制轉(zhuǎn)換和裝箱/取消裝箱外,它還允許變量arity調(diào)用。
在實(shí)踐中,這允許我們從一個(gè)int
值數(shù)組開(kāi)始創(chuàng)建一個(gè)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));
調(diào)用Exact
如果我們想在執(zhí)行方法句柄的方式上更加嚴(yán)格(參數(shù)的數(shù)量及其類(lèi)型),我們必須使用invokeExact()
方法。
事實(shí)上,它沒(méi)有為所提供的類(lèi)提供任何類(lèi)型轉(zhuǎn)換,并且需要固定數(shù)量的參數(shù)。
讓我們看看如何使用方法句柄對(duì)兩個(gè)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
方法傳遞一個(gè)不是int
的數(shù)字,那么調(diào)用將導(dǎo)致WrongMethodTypeException
。
使用數(shù)組
MethodHandles不僅用于字段或?qū)ο螅€用于數(shù)組。事實(shí)上,使用asSpreader()
API,可以生成一個(gè)數(shù)組擴(kuò)展方法句柄。
在這種情況下,方法句柄接受一個(gè)數(shù)組參數(shù),將其元素?cái)U(kuò)展為位置參數(shù),并可以選擇數(shù)組的長(zhǎng)度。
讓我們看看如何擴(kuò)展方法句柄來(lái)檢查數(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" }));
增強(qiáng)方法句柄
一旦我們定義了一個(gè)方法句柄,就可以通過(guò)將方法句柄綁定到一個(gè)參數(shù)來(lái)增強(qiáng)它,而無(wú)需實(shí)際調(diào)用它。
例如,在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增強(qiáng)功能
在Java9中,對(duì)MethodHandles API進(jìn)行了一些增強(qiáng),目的是使其更易于使用。
這些增強(qiáng)影響了3個(gè)主要主題:
- 查找函數(shù)–允許從不同上下文中查找類(lèi),并支持接口中的非抽象方法
- 參數(shù)處理——改進(jìn)參數(shù)折疊、參數(shù)收集和參數(shù)傳播功能
- 附加組合–添加循環(huán)(
loop
、whileLoop
、doWhileLoop
…),并通過(guò)tryFinally
提供更好的異常處理支持
這些變化帶來(lái)了一些額外的好處:
- 增加JVM編譯器優(yōu)化
- 實(shí)例化減少
- 在使用MethodHandles API時(shí)啟用了精度
結(jié)論
在本文中,我們介紹了MethodHandles API、它們是什么以及如何使用它們。
我們還討論了它與反射API的關(guān)系,由于方法句柄允許低級(jí)別操作,因此最好避免使用它們,除非它們完全適合工作范圍。
以上就是Java MethodHandles介紹與反射對(duì)比區(qū)別詳解的詳細(xì)內(nèi)容,更多關(guān)于Java MethodHandles對(duì)比反射的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java必學(xué)必會(huì)之網(wǎng)絡(luò)編程
java必學(xué)必會(huì)之網(wǎng)絡(luò)編程,學(xué)習(xí)了解java網(wǎng)絡(luò)編程、網(wǎng)絡(luò)通信協(xié)議、TCP協(xié)議和UDP協(xié)議,對(duì)各個(gè)協(xié)議進(jìn)行深入學(xué)習(xí),做到必學(xué)必會(huì)2015-12-12深入了解Java數(shù)據(jù)結(jié)構(gòu)和算法之堆
這篇文章主要為大家介紹了Java數(shù)據(jù)結(jié)構(gòu)和算法之堆 ,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-01-01淺談Java內(nèi)存區(qū)域劃分和內(nèi)存分配策略
這篇文章主要介紹了淺談Java內(nèi)存區(qū)域劃分和內(nèi)存分配策略,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05在logback.xml中自定義動(dòng)態(tài)屬性的方法
這篇文章主要介紹了在logback.xml中自定義動(dòng)態(tài)屬性的方法,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-08-08java實(shí)現(xiàn)簡(jiǎn)單的圖書(shū)管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡(jiǎn)單的圖書(shū)管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-07-07使用Postman自動(dòng)生成Cookie并轉(zhuǎn)換為Java代碼的實(shí)現(xiàn)
在接口測(cè)試中,有時(shí)候需要在請(qǐng)求中攜帶Cookie信息,為了方便測(cè)試,我們可以使用Postman來(lái)自動(dòng)生成Cookie,并將其轉(zhuǎn)換為Java代碼,以便在自動(dòng)化測(cè)試中使用,下面將介紹如何實(shí)現(xiàn)這一功能,需要的朋友可以參考下2024-11-11解決出現(xiàn) java.lang.ExceptionInInitializerError錯(cuò)誤問(wèn)題
這篇文章主要介紹了解決出現(xiàn) java.lang.ExceptionInInitializerError錯(cuò)誤問(wèn)題的相關(guān)資料,需要的朋友可以參考下2017-01-01詳解獲取Spring MVC中所有RequestMapping以及對(duì)應(yīng)方法和參數(shù)
本篇文章主要介紹了詳解獲取Spring MVC中所有RequestMapping以及對(duì)應(yīng)方法和參數(shù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-03-03