MethodHandle简单使用

原文来自:fair-jm.iteye.com 转截请注明出处

 

最近看了一些MethodHandle的使用 东西很杂 七拼八凑 有一些自己的理解可能有错误

因为是七拼八凑的 一些来源我已经记不清楚了...以下买标注哪里的主要来自于《深入理解java7》的迷你书 还有小部分是API或者网上其他的文章中的

 

 

http://www.jdon.com/idea/java/invokedynamic.html 写道
invokedynamic字节码则改变了这种方式,JVM允许其在运行时再进行方法的这种绑定检查,这样,你能够拦截一个根本不存在的方法调用,然后将控制流程转移到另外一个方法里,做些其他事情

MethodHandle 允许对一个方法产生引用,如同对象引用一样,这样避免了繁重的反射。

性能更好 创建MethodHandle时就实现方法检测 而不是调用invoke()时

一般Java类都在编译时检查类型,而invokedynamic的调用不是在编译阶段检查,而是在运行时检查。要做到这点,我们需要定义一个bootstrap,这样在不存在的方法和我们实际方法搭起桥梁。

当我们通过InvokeDynamic调用不存在的方法getDate()时, JVM会使用bootstrap方法来获得真正方法的MethodHandle,然后调用它。JVM缓存了MethodHandle 能提高性能,第一次动态调用将在bootstrap方法中搜索。当然,首先得注册告诉JVM我们的bootstrap方法, 这时得用Linkage.registerBootstrapMethod(String methodName) 方法.                                                                 //注:在现在的JDK7中并未有InvokeDynamic这个类....Linkage似乎也被移除了...

 

Java虚拟机规范 (周志明大大等翻译的版本) 写道
invokevirtual指令用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),这也是Java语言中最常见的方法分派方式。

invokeinterface指令用于调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用。

invokespecial指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法(§2.9)、私有方法和父类方法。

invokestatic指令用于调用类方法(static方法)。

   介绍这个主要是MethodHandlers.LookUp类中的一些方法和这些有点联系。

   关于以上这些指令在代码中具体的出现请见下面的例子:

  

Java代码   收藏代码
  1. interface SampleInterface {    
  2.     void sampleMethodInInterface();    
  3. }    
  4.    
  5. class S implements SampleInterface {    
  6.     public void sampleMethodInInterface() {}    
  7.     public void normalMethod() {}    
  8.     public void fatherMethod() {}    
  9.     public static void staticSampleMethod() {}    
  10. }  
  11.    
  12. class Sample extends S {    
  13.     public void sampleMethodInInterface() {}    
  14.     public void normalMethod() {}    
  15.     public static void staticSampleMethod() {}    
  16. }    
  17.   
  18. class MethodInvokeTypes {    
  19.     public void invoke() {    
  20.         S sample = new Sample();          ---> 4: invokespecial #3    // Method Sample."<init>":()V  
  21.         sample.sampleMethodInInterface(); ---> 9: invokevirtual #4    // Method S.sampleMethodInInterface:()V  
  22.         Sample newSample = new Sample();  --->16: invokespecial #3    // Method Sample."<init>":()V  
  23.         newSample.normalMethod();         --->21: invokevirtual #5    // Method Sample.normalMethod:()V  
  24.         Sample.staticSampleMethod();      --->24: invokestatic  #6    // Method Sample.staticSampleMethod:()V  
  25.         newSample.fatherMethod();         --->28: invokevirtual #7    // Method Sample.fatherMethod:()V  
  26.     }    
  27. }   

 

 

 

方法调用的流程:

1)名称:要调用的方法的名称一般是由开发人员在源代码中指定的符号名称。这个名称同样会出现在编译之后的字节代码中。

2)链接:链接包含了要调用方法的类。这一步有可能会涉及类的加载。

3)选择:选择要调用的方法。在类中根据方法名称和参数选择要调用的方法。

4)适配:调用者和接收者对调用的方式达成一致,即对方法的类型声明达成共识。

 

MethodHandle实例:

 

Java代码   收藏代码
  1.       String a="abcd";  
  2. MethodType mt=MethodType.methodType(String.class,int.class,int.class);  
  3. MethodHandle handle=MethodHandles.lookup().findVirtual(String.class,"substring",mt);  
  4. System.out.println(handle.invoke(a,1,2)); //输出b  

 除了invoke方法可以调用外,要注意invokeExact()这个要求严格匹配的方法:

 

以上改成:handle.invokeExact(a,1,2) 是错误的 一定要写成(String)handle.invokeExact(a,1,2)

 

 

MethodType的一些使用:

 

Java代码   收藏代码
  1. MethodType mt=MethodType.methodType(void.class,int.class,double.class);  
  2. System.out.println(mt);  
  3. System.out.println(mt.wrap());  
  4. System.out.println(mt.unwrap());  
  5. System.out.println(mt.generic());  
  6. System.out.println(mt.toMethodDescriptorString());  
  7. System.out.println(mt.erase());  
  8. 输出:  
  9. (int,double)void  
  10. (Integer,Double)Void  
  11. (int,double)void  
  12. (Object,Object)Object  
  13. (ID)V  

 

 

 

句柄获取:

获得方法句柄通过java.lang.invoke.MethodHandles.Lookup类来完成

findConstructor就是查找构造器的

findVirtual就是查找一般函数的(同invokeVirtual)

findStatic 就是查找静态方法的(同invokeStatic)

以及findSpecial查找私有方法的

获取属性的话通过findGetter或者fingStaticGetter就可以了

 

 

findConstructor的演示(内部类):

 

Java代码   收藏代码
  1. (这是一个错误的例子):  
  2. public class TestMH {  
  3.       
  4.     class Person{  
  5.         private String name;  
  6.         private int age;  
  7.         public String getName() {  
  8.             return name;  
  9.         }  
  10.         public void setName(String name) {  
  11.             this.name = name;  
  12.         }  
  13.         public int getAge() {  
  14.             return age;  
  15.         }  
  16.         public void setAge(int age) {  
  17.             this.age = age;  
  18.         }  
  19.     }  
  20.       
  21.     public static void main(String[] args) throws Throwable {  
  22.         MethodHandles.Lookup lookup=MethodHandles.lookup();  
  23.         MethodHandle mh=lookup.findConstructor(Person.class, MethodType.methodType(void.class));  
  24.         Person p=(Person)mh.invokeExact();  
  25.         System.out.println(p);  
  26.     }  
  27.   
  28. }  

 这样的代码会提示:

 

Exception in thread "main" java.lang.NoSuchMethodException: no such constructor: com.cc.dynamic.TestMH$Person.<init>()void/newInvokeSpecial

原因很简单..内部类默认会把外部类的实例传入构造方法 那么自然就不会有参数为空的构造方法了 如下:

 // Method descriptor #12 (Lcom/cc/dynamic/TestMH;)V

  // Stack: 2, Locals: 2

  TestMH$Person(com.cc.dynamic.TestMH arg0);

在内部类里的方法中都会加入这个参数 如果加个String的参数的构造函数进去:

public TestMH$Person(com.cc.dynamic.TestMH arg0, java.lang.String name);

改正也很简单:

 

Java代码   收藏代码
  1. public static void main(String[] args) throws Throwable {  
  2.     MethodHandles.Lookup lookup=MethodHandles.lookup();  
  3.     MethodHandle mh=lookup.findConstructor(Person.class, MethodType.methodType(void.class,TestMH.class));  
  4.     Person p=(Person)mh.invokeExact(new TestMH());  
  5.     System.out.println(p);  
  6. }  

 

 

 

findSpecial演示:

 

Java代码   收藏代码
  1. public class TestMH {  
  2.   
  3.     private String privateInfo(){  
  4.         return "10";  
  5.     }  
  6.       
  7.     public static void main(String[] args) throws Throwable {  
  8.         MethodHandle mh=MethodHandles.lookup().findSpecial(TestMH.class"privateInfo", MethodType.methodType(String.class),TestMH.class);  
  9.         System.out.println(mh.invoke(new TestMH()));  
  10.     }  
  11. }  

 如果调用其它类里的私有方法 则会出现错误 

 

把参数写成:

 

Java代码   收藏代码
  1. //Person中的私有方法  
  2. MethodHandle mh=MethodHandles.lookup().findSpecial(Person.class"privateInfo", MethodType.methodType(String.class),Person.class);  

 或者:

 

 

Java代码   收藏代码
  1. //Person中的私有方法  
  2. MethodHandle mh=MethodHandles.lookup().findSpecial(Person.class"privateInfo", MethodType.methodType(String.class),TestMH.class);  

 都会出错

 

有哪位可以告知下为什么吗?

折衷的方式可以通过反射:

 

Java代码   收藏代码
  1. Method m=Person.class.getDeclaredMethod("privateInfo");  
  2. m.setAccessible(true); //破坏掉  
  3. MethodHandle mh=MethodHandles.lookup().unreflect(m);  
  4. System.out.println(mh.invoke(new Person()));  

 

 

 

findGetter演示:

 

Java代码   收藏代码
  1. MethodHandles.Lookup lookUp=MethodHandles.lookup();  
  2. MethodHandle mh=lookUp.findGetter(Person.class"name",String.class);  
  3. System.out.println(mh.invoke(new Person()));  

Person的name是public的 private会报错(没有访问权限) 这种方式获取的域是可以写成 t.x的形式域

 

 

 

 

通过其他方式生成MethodHandle:

 

Java代码   收藏代码
  1. public static void main(String[] args) throws Throwable {  
  2.     MethodHandle mh=MethodHandles.constant(String.class"hello");  
  3.     System.out.println((String)mh.invokeExact());  
  4. }  //这种方式生成一个返回指定类型的内容的方法句柄  
  5.   
  6.   MethodHandle mh=MethodHandles.identity(String.class);  
  7.   System.out.println((String)mh.invokeExact("hello"));  //这种方式生成一种输入什么返回什么的方法句柄  

 

 

 

函数柯里化的实现:

 

Java代码   收藏代码
  1. public static MethodHandle curry(int number,MethodHandle mh){  
  2.     return MethodHandles.insertArguments(mh, 0, number);  
  3.       
  4. }  
  5. public static int add(int a,int b){  
  6.     return a+b;  
  7. }  
  8. public static void main(String[] args) throws Throwable {  
  9.     MethodType mt=MethodType.methodType(int.class,int.class,int.class);  
  10.     MethodHandle mh=MethodHandles.lookup().findStatic(TestMH.class,"add",mt);  
  11.     mh=curry(10,mh); //让int add(int,int) 变为int add(5,int)  
  12.     System.out.println(mh.invoke(10));  
  13. }  

 

 

 

 

MethodHandles中一些其他的方法:

dropArguments:用来忽略传递的参数的 第一个参数是MethodHandle对象  第二个参数是忽略参数的起始位置,接下去的参数是忽略参数的类型(和传入的要匹配)

(代码来自API中)

 

Java代码   收藏代码
  1. public void testDrop() throws Throwable{  
  2.  MethodHandle cat = MethodHandles.lookup().findVirtual(String.class,  
  3.       "concat", MethodType.methodType(String.class, String.class));  
  4.     assertEquals("xy", (String) cat.invokeExact("x""y"));  
  5.     MethodHandle d0 = dropArguments(cat, 0, String.class);  
  6.     assertEquals("yz", (String) d0.invokeExact("x""y""z"));  
  7.     MethodHandle d1 = dropArguments(cat, 1, String.class);  
  8.     assertEquals("xz", (String) d1.invokeExact("x""y""z"));  
  9.     MethodHandle d2 = dropArguments(cat, 2, String.class);  
  10.     assertEquals("xy", (String) d2.invokeExact("x""y""z"));  
  11.     MethodHandle d12 = dropArguments(cat, 1int.classboolean.class);  
  12.     assertEquals("xz", (String) d12.invokeExact("x"12true"z"));  
  13. }  

 

 

 

filterArguments:过滤参数的 与其说是过滤其实是对参数进行一下预处理

Java代码   收藏代码
  1. @Test  
  2.  public void testFilter() throws Throwable{  
  3.   MethodHandle cat = lookup().findVirtual(String.class,  
  4.           "concat", MethodType.methodType(String.class, String.class));  
  5.         MethodHandle upcase = lookup().findVirtual(String.class,  
  6.           "toUpperCase", MethodType.methodType(String.class));  
  7.         assertEquals("xy", (String) cat.invokeExact("x""y"));  
  8.         MethodHandle f0 = filterArguments(cat, 0, upcase);  
  9.         assertEquals("Xy", (String) f0.invokeExact("x""y")); // Xy  
  10.         MethodHandle f1 = filterArguments(cat, 1, upcase);  
  11.         assertEquals("xY", (String) f1.invokeExact("x""y")); // xY  
  12.         MethodHandle f2 = filterArguments(cat, 0, upcase, upcase);  
  13.         assertEquals("XY", (String) f2.invokeExact("x""y")); // XY  

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值