最近在讀《深刻理解java虛擬機》第二版,在JDK1.8環境下遇到一個關於MethodHandle使用上的問題,在這裏記錄下。https://github.com/floor07/essential-jvm (github jvm的讀書筆記)html
本文目錄以下:java
在《深刻理解java虛擬機》第二版第8章,上提出了一個問題,簡要描述以下:
在Son類中,調用GrandFather的thinking方法,打印 I 'm grandFather。git
Son類,GrandFather類定義以下:github
public class MethodHandleTest { class GrandFather{ void thinking(){ System.out.println("I 'm grandFather!"); } } class Father extends GrandFather{ void thinking(){ System.out.println("I 'm father!"); } } class Son extends Father{ void thinking(){ //實現祖父類的thinking(),打印 I 'm grandFather } } }
針對這個問題,書中引出了java.lang.invoke包,下面簡要介紹下api
JDK1.7以後,加入的java.lang.invoke包,該包提供了一種新的肯定動態目標方法的機制,Method Handle.
Method Handle使得Java擁有了相似函數指針或委託的方法別名的工具。安全
舉例說明以下: oracle
例如 咱們嘗試調用:System.out.println的方法,該方法在JDK中的定義以下:jvm
public void println(String x){ ... }
獲取System.out.println的MethodType對象,以下ide
/** * MethodType表明方法的類型,包含方法的返回值(第一個參數),以後是按順序的方法接收的參數 * */ MethodType mt= MethodType.methodType(void.class,String.class);
獲取System.out.println的MethodHandle的(這裏邊有一個findVirtual方法,是用於執行虛方法的。)方式以下:函數
/** * lookup方法來自於MethodHandles.Lookup * 用於在指定類(第一個參數),指定方法名稱(第二個參數),指定方法類型(第三參數)查找 * 符合訪問權限的方法句柄 * **/ /** * 由於這裏調用的是一個虛方法, * 按照java語言的規範,第一個參數是隱式的表明該該方法的接收者,就是this,這裏由bindTo方法進行處理。 * **/ MethodHandles.lookup().findVirtual(receiver.getClass(),"println",mt).bindTo(receiver);
MethodHandle方法 | 字節碼 | 描述 |
findStatic | invokestatic | 調用靜態方法 |
findSpecial | invokespecial | 調用實例構造方法,私有方法,父類方法。 |
findVirtual | invokevirtual | 調用全部的虛方法 |
findVirtual | invokeinterface | 調用接口方法,會在運行時再肯定一個實現此接口的對象。 |
看了上邊的簡要說明,很天然的想法就是MethodType先描述下thinking方法,
以後使用MethodHandles.lookup()的findSpecial方法,在GrandFather上查找thinking方法進行執行。
書上的解法也相似,下面我們就看看書上的解法。
public class MethodHandleTest { class GrandFather{ void thinking(){ System.out.println("I 'm grandFather!"); } } class Father extends GrandFather{ void thinking(){ System.out.println("I 'm father!"); } } class Son extends Father{ void thinking() { //實現祖父類的thinking(),打印 I 'm grandFather MethodType mt=MethodType.methodType(void.class); try { MethodHandle md=MethodHandles.lookup().findSpecial(GrandFather.class, "thinking", mt,this.getClass()); md.invoke(this); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (Throwable e) { e.printStackTrace(); } } } public static void main(String[] args) { MethodHandleTest.Son son=new MethodHandleTest().new Son(); son.thinking(); } }
上述代碼在JDK1.7.0_09上運行正常,運行結果是I'm grandFather
可是 **該解法在JDK1.8下不行**,運行結果是I’m father
爲何1.8跟預想的不一致?帶着這個疑問我查閱了JDK8規範說明
本人摘錄其中的一段文字說明以下:
A lookup class which needs to create method handles will call MethodHandles.lookup to create a factory for itself.
When the Lookup factory object is created, the identity of the lookup class is determined,
and securely stored in the Lookup object.
The lookup class (or its delegates) may then use factory methods on the Lookup object to create method handles
for access-checked members.
This includes all methods, constructors, and fields which are allowed to the lookup class, even private ones.
簡要翻譯以下:
須要建立method handles的查找類將調用MethodHandles.lookup爲它本身建立一個工廠。
當該工廠對象被查找類建立後,查找類的標識,安全信息將存儲在其中。
查找類(或它的委託)將使用工廠方法在被查找對象上依據查找類的訪問限制,建立method handles。
可建立的方法包括:查找類全部容許訪問的全部方法、構造函數和字段,甚至是私有方法。
簡單說就是 :JDK1.8下MethodHandles.lookup是調用者敏感的,不一樣調用者訪問權限不一樣,其結果也不一樣。
在本例中,在Son類中調用MethodHandles.lookup,受到Son限制,僅僅能訪問到Father類的thinking。因此結果是:'I'm father'
在這裏,各位看官,心中必定會有一個疑問:這個包與java.lang.reflecct包區別是什麼?
Java一致在更新,也愈來愈嚴禁,看書時,必定要注意對比最新的官方文檔。
JDK1.8環境下MethodHandles.lookup方法是調用者敏感的。