譯者 | 晶顏
審校 | 重樓
區(qū)分資深Java開發(fā)人員的因素之一是熟悉反射(reflection)及其先進(jìn)替代品。反射為Java開發(fā)者提供了“超能力”,但它很麻煩,容易出錯(cuò),并且存在性能瓶頸。現(xiàn)代Java正在努力用標(biāo)準(zhǔn)化的選項(xiàng)取代反射,包括方法句柄(MethodHandle)和變量句柄(VarHandle)。與反射一樣,這些類也允許你訪問對(duì)象上的方法和字段,但使用的是更清晰的API。
句柄的力量
顧名思義,MethodHandle和VarHandle都為你提供了“句柄”,它們是引用對(duì)象元屬性的變量。這些句柄使你能夠直接處理方法和字段。它們是特殊的變量,引用運(yùn)行時(shí)環(huán)境的某些部分,否則將對(duì)代碼隱藏。
這些功能的起點(diǎn)是MethodHandle上的各種查找方法,這些方法提供了一種以編程方式查找類元數(shù)據(jù)的現(xiàn)代方法。這類似于舊的反射API的方法,如getDeclaredMethod,但具有更多的結(jié)構(gòu)和安全性。
一旦有了類元數(shù)據(jù)的句柄,就可以使用MethodHandle和VarHandle以編程方式對(duì)類實(shí)例上存在的方法和字段進(jìn)行調(diào)用。在底層,JVM管理著這些方法,通常會(huì)比使用反射獲得更好的性能。
方法和變量句柄 VS. Java反射
要真正理解MethodHandles和Varhandles——它們是做什么的以及它們?yōu)槭裁从杏谩私庖恍╆P(guān)于Java反射的知識(shí)是有幫助的。這將幫助你理解為什么反射會(huì)演變成這些較新的API。
最根本的問題是:這些技術(shù)——反射、方法句柄、變量句柄——需要滿足什么需求?當(dāng)我們可以簡(jiǎn)單地實(shí)例化一個(gè)對(duì)象,調(diào)用它的公共方法并訪問它的公共成員時(shí),我們?yōu)槭裁匆镁幊痰姆绞絹?lái)做這些事情呢?
在很多情況下,你不能通過公共方法訪問你需要的程序,所以必須繞過正常的路線。這主要發(fā)生在你編寫類似框架代碼時(shí),該代碼對(duì)一系列類進(jìn)行操作并對(duì)它們進(jìn)行非標(biāo)準(zhǔn)操作。
以一個(gè)持久性框架為例,你需要將類映射到表和表之間,因此你需要內(nèi)省(Introspect)類以了解它們具有哪些字段和方法。這種情況也會(huì)出現(xiàn)在應(yīng)用程序代碼中,特別是當(dāng)你需要訪問遺留庫(kù)中無(wú)法訪問的部分時(shí)。
決定使用哪種技術(shù)要先了解需要什么。如果你可以使用普通的Java調(diào)用來(lái)解決這個(gè)問題,那么它就是可行的方法。如果你需要更復(fù)雜的東西,先去看看標(biāo)準(zhǔn)的API(比如MethodHandles和VarHandles)。只有當(dāng)這些都無(wú)法實(shí)現(xiàn)時(shí),你才應(yīng)該轉(zhuǎn)而依靠反射。
下述示例可以幫助你理解為什么JAVA開發(fā)工具包(JDK)更喜歡句柄而非傳統(tǒng)的Java反射。
使用反射來(lái)訪問方法
我們將從一個(gè)反射示例開始,因?yàn)樗?/span>常見,并且會(huì)給我們一個(gè)已知的參考。請(qǐng)記住,這是最后的解決方案。
假設(shè)你有這個(gè)類:
public class MyClass {
private String name;
public MyClass(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
這是一個(gè)非常簡(jiǎn)單的事情:只是一個(gè)用于保存字符串名的類。要?jiǎng)?chuàng)建這個(gè)類,我們可以使用普通實(shí)例化:
MyClass objectInstance = new MyClass("John Doe");
下面是使用反射訪問該方法的示例:
Class<?> clazz = objectInstance.getClass();
Method method = clazz3.getDeclaredMethod("getName");
String value = (String) method.invoke(objectInstance);
System.out.println(value); // prints "John Doe"
使用MethodHandles來(lái)訪問方法
方法句柄為我們提供了與反射相同的功能,但語(yǔ)法更安全:
Class<?> clazz = objectInstance.getClass();
MethodHandle handle = MethodHandles.lookup().findVirtual(clazz, "getName", methodType(String.class));
String value = (String) handle.invoke(objectInstance);
System.out.println(value); // Prints “John Doe”
我們以同樣的方式開始,從實(shí)例中獲取類。然后,我們?cè)贛ethodHandles上使用lookup(). findvirtual()方法。這是MethodHandles設(shè)計(jì)的主要目的之一:提供一種更簡(jiǎn)潔、JDK認(rèn)可的方法來(lái)查找方法。這種方法還針對(duì)JVM優(yōu)化進(jìn)行了增強(qiáng)。
接下來(lái),我們將使用handle.invoke調(diào)用帶有句柄的方法,并傳入對(duì)象實(shí)例。
直接訪問字段
假設(shè)我們之前的類(MyClass)上面有name字段但沒有訪問器。我們現(xiàn)在需要更強(qiáng)的程序來(lái)訪問它,因?yàn)槲覀円苯釉L問私有成員(Private Member)。下面是我們使用標(biāo)準(zhǔn)反射的方法:
Class<?> clazz = objectInstance.getClass();
Field field = clazz.getDeclaredField("name");
field.setAccessible(true);
String value = (String) field.get(objectInstance);
System.out.println(value); // prints “John Doe”
注意,我們?cè)俅沃苯犹幚韺?duì)象的元數(shù)據(jù),比如它的類和它的字段。我們可以使用setAccessible操作字段的可訪問性(這被認(rèn)為是有風(fēng)險(xiǎn)的,因?yàn)樗赡軙?huì)改變目標(biāo)代碼所寫的限制)。這是使私有字段對(duì)我們可見的關(guān)鍵部分。
現(xiàn)在讓我們使用變量句柄做同樣的事情:
Class<?>l clazz = objectInstance.getClass();
VarHandle handle = MethodHandles.privateLookupIn(clazz,
MethodHandles.lookup()).findVarHandle(clazz, "name", String.class);
String value = (String) handle.get(objectInstance);
System.out.println(value4); // prints "John Doe"
這里,我們使用privateLookupIn,因?yàn)樵撟侄伪粯?biāo)記為私有Private)。還有一個(gè)通用lookup(),它將尊重訪問修飾符,因此它更安全,但不會(huì)找到私有字段。
雖然上面的代碼可以運(yùn)行,但出于性能原因,建議靜態(tài)地實(shí)例化句柄本身,如下所示:
private static VarHandle HANDLE;
static {
try {
HANDLE = MethodHandles.privateLookupIn(MyClass.class, MethodHandles.lookup()).findVarHandle(MyClass.class, "name", String.class);
} catch (Throwable t){
throw new RuntimeException(t);
}
}
// …
System.out.println("static: " + HANDLE.get(objectInstance));
這里,我們靜態(tài)地實(shí)例化了HANDLE變量,然后在稍后的正常代碼流中使用它。這也突出了句柄本身是為類型(MyClass)定義的,然后為實(shí)例(ObjectInstance)重用。
注意,直接實(shí)例化句柄需要知道類的名稱。如果你不知道類的名稱,則不能使用這種方法。
方法和變量句柄的限制
盡管它們?yōu)闃?biāo)準(zhǔn)化的JDK帶來(lái)了強(qiáng)大的功能,但方法句柄和變量句柄并不打算涵蓋Java反射API中的所有功能。它們只是涵蓋了一個(gè)重點(diǎn)范圍:查找類元數(shù)據(jù)并使用它來(lái)訪問常規(guī)Java限制之外的方法和字段。其余sun.misc. Unsafe中的反射力量正逐漸被其他包所取代。
如前所述,MethodHandles和VarHandle不支持實(shí)例化類,這在某些情況下會(huì)產(chǎn)生限制。
是時(shí)候思考反射替代方案了
花點(diǎn)時(shí)間說服自己遠(yuǎn)離反射是必要的,也是值得的。如果你研究了基準(zhǔn)測(cè)試,就會(huì)發(fā)現(xiàn)方法句柄和變量句柄的性能普遍優(yōu)于反射。另一方面,它們更安全、更地道,并且JVM代碼庫(kù)正在采用這些方法,它們的普及也只是時(shí)間問題而已。
在基準(zhǔn)測(cè)試中,靜態(tài)聲明句柄可以顯著提高性能。這是因?yàn)镴VM可以在編譯時(shí)內(nèi)聯(lián)這些信息。但是,如前所述,這樣做并非總是可行的——例如,如果你在編譯時(shí)不知道類的名稱。
除了性能之外,基于正確性等因素考慮,反射也正逐漸被棄用。最終,無(wú)論如何都需要遷移工作。現(xiàn)在是時(shí)候開始移動(dòng)代碼庫(kù)中那些具有現(xiàn)代替代品(如MethodHandles和VarHandle)的部分了!
原文標(biāo)題:Better than reflection: Using method handles and variable handles in Java,作者:Matthew Tyson