JDK1.8下關於MethodHandle問題

最近在讀《深刻理解java虛擬機》第二版,在JDK1.8環境下遇到一個關於MethodHandle使用上的問題,在這裏記錄下。https://github.com/floor07/essential-jvm (github jvm的讀書筆記)html

本文目錄以下:java

  • 引子
  • java.lang.invoke簡介
  • 關於引子書上的解法
  • JDK1.8爲何跟預想的不一致

引子

在《深刻理解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

 

簡介java.lang.invoke包


JDK1.7以後,加入的java.lang.invoke包,該包提供了一種新的肯定動態目標方法的機制,Method Handle.
Method Handle使得Java擁有了相似函數指針或委託的方法別名的工具。安全

簡單使用方式

 

  •  建立目標方法的MethodType對象,MethodType.methodType方法的第一個參數是返回值 ,以後是按目標方法接收的參數的順序填寫參數類型。  
  •  MethodHandles.lookup()對應的findXXX方法,獲取目標方法的MethodHandle對象。  
  •  調用MethodHandle對象的invokeExact方法。該方法參數是目標方法的參數。  

舉例說明以下:  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);


MethodHandles.Lookup的findXXX方法說明

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

JDK1.8爲何跟預想的不一致?

爲何1.8跟預想的不一致?帶着這個疑問我查閱了JDK8規範說明

詳細信息請查閱
https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/MethodHandles.Lookup.html#findSpecial-java.lang.Class-java.lang.String-java.lang.invoke.MethodType-java.lang.Class  


 本人摘錄其中的一段文字說明以下:  


 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.lang.reflecct包的區別

 

  • MethodHandle服務於全部java虛擬機上的語言,Reflection僅僅服務於java語言。
  • Reflection在模擬Java代碼層次的調用,而MethodHandle在模擬字節碼層次的方法調用。
  • Reflection是重量級,而MethodHandle是輕量級。
  • MethodHandle能夠進行內聯優化,Reflection徹底沒有。

總結

Java一致在更新,也愈來愈嚴禁,看書時,必定要注意對比最新的官方文檔。

JDK1.8環境下MethodHandles.lookup方法是調用者敏感的。

相關文章
相關標籤/搜索