理解 JDK 中的 MethodHandle

作者:梁德泉

Java是一个支持反射的语言,从诞生的那一刻起就已经支持了反射。经典的反射代码可以这样写:

public class Main {
    public static void main(String[] args) throws Throwable {
        Method m = Super.class.getMethod("x", boolean.class);
        m.invoke(new Super(), false); // super::primitive
        m.invoke(new Super(), Boolean.FALSE); // super::primitive
        m.invoke(new Sub(), false); // sub::primitive
        m.invoke(new Sub(), Boolean.FALSE); // sub::primitive

        m = Sub.class.getMethod("x", Boolean.class);
        // m.invoke(new Super(), false); // IllegalArgumentException: object is not an instance of declaring class
        m.invoke(new Sub(), false); // sub::boxed
        m.invoke(new Sub(), Boolean.FALSE); // sub::boxed

        // m.invoke(null, false); // NullPointerException
        // m.invoke(new Sub()); // IllegalArgumentException: wrong number of arguments
    }
}
class Super {
    public int x(boolean a) {System.out.println("super::primitive");return 1;}
    public int x(Boolean a) {System.out.println("super::boxed");return 1;}
}
class Sub extends Super {
    public int x(boolean a) {System.out.println("sub::primitive");return 1;}
    public int x(Boolean a) {System.out.println("sub::boxed");return 1;}
}

从上述代码可以看出,可以利用反射在运行时通过名字查找方法句柄,动态调用想要的方法。同时,上面的例子中也可以发现,通过Method反射调用是支持多态的。

一切完美。但是从Java7开始,JDK中又多了一个功能类似的成员,java.lang.invoke.MethodHandle。先来看看MethodHandle又是怎么用的:

public class MHTest {
    public static void main(String[] args) throws Throwable {
        MethodType mt = MethodType.methodType(int.class, Boolean.class);
        MethodHandle mh = MethodHandles.lookup().findVirtual(MHSuper.class, "x", mt);
        Object a;
//        mh.bindTo(null).invoke(false); // NullPointerException
//        mh.invoke(false); // WrongMethodTypeException: cannot convert MethodHandle(MHSuper,Boolean)int to (boolean)void
//        mh.bindTo(new Object()).invoke(false); // ClassCastException: Cannot cast java.lang.Object to MHSuper
        mh.bindTo(new MHSuper()).invoke(false); // super::boxed
        mh.bindTo(new MHSuper()).invoke(Boolean.FALSE); // super::boxed
        a = (int)mh.bindTo(new MHSuper()).invokeExact(Boolean.FALSE); // super::boxed
//        a = (Number)mh.bindTo(new MHSuper()).invokeExact(Boolean.FALSE); // WrongMethodTypeException: expected (Boolean)int but found (Boolean)Number
//        a = (Integer)mh.bindTo(new MHSuper()).invokeExact(Boolean.FALSE); // WrongMethodTypeException: expected (Boolean)int but found (Boolean)Integer
//        mh.bindTo(new MHSuper()).invokeExact(Boolean.FALSE); // WrongMethodTypeException: expected (Boolean)int but found (Boolean)void

        mh.bindTo(new MHSub()).invoke(false); // sub::boxed
        mh.bindTo(new MHSub()).invoke(Boolean.FALSE); // sub::boxed
        a = (int)mh.bindTo(new MHSub()).invokeExact(Boolean.FALSE); // sub::boxed

        mh = MethodHandles.lookup().findStatic(MHSuper.class, "y", mt);
        mh.invoke(false); // super::static
        a = (int)mh.invokeExact(Boolean.FALSE); // super::static

        mh = MethodHandles.lookup().findStatic(MHSuper.class, "z", MethodType.methodType(int.class, MHSuper.class));
        MHSuper sup = new MHSub();
        a = (int)mh.invokeExact(sup); // class MHSub
//        MHSub sub = new MHSub();a = (int)mh.invokeExact(sub); // WrongMethodTypeException: expected (MHSuper)int but found (MHSub)int
    }
}

class MHSuper {
    public static int y(Boolean a) {System.out.println("super::static");return 1;}
    public static int z(MHSuper a) {System.out.println(a.getClass());return 1;}
    public int x(boolean a) {System.out.println("super::primitive");return 1;}
    public int x(Boolean a) {System.out.println("super::boxed");return 1;}
}
class MHSub extends MHSuper {
    public static int y(Boolean a) {System.out.println("sub::static");return 1;}
    public int x(boolean a) {System.out.println("sub::primitive");return 1;}
    public int x(Boolean a) {System.out.println("sub::boxed");return 1;}
}

MethodHandle反射调用也是支持多态的,并且和Method不同的是,MethodHandle的成员方法要线bindTo到某个instance,bind过程中已经做了类型检查;而Method成员方法左值是和函数参数一起传入的。注意到MethodHandle比Method多了一个invokeExact方法,它与invoke的区别是方法参数、返回值匹配非常严格,调用时如果有数值参数隐式转换(如short转int/子类转父类)、装箱拆箱,会直接抛异常。

在Java源码层面/运行表现层面来看,两者区别并不大,那为什么会在JDK在新引入、MethodHandle这个特性呢?先从字节码看一看区别:

// Method
// method.invoke(new Super(), false);
invokevirtual #37                 // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
// (int)method.invoke(new Super(), false);
invokevirtual #44                 // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
// method.invoke(new Super(), Boolean.FALSE);
invokevirtual #51                 // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;

// MethodHandle
// mh.bindTo(new MHSuper()).invoke(false);
invokevirtual #56                 // Method java/lang/invoke/MethodHandle.invoke:(Z)V
// mh.bindTo(new MHSuper()).invoke(Boolean.FALSE);
invokevirtual #77                 // Method java/lang/invoke/MethodHandle.invoke:(Ljava/lang/Boolean;)V
// (int)mh.bindTo(new MHSuper()).invoke(false);
invokevirtual #60                 // Method java/lang/invoke/MethodHandle.invoke:(Z)I
// (Integer)mh.bindTo(new MHSuper()).invoke(false);
invokevirtual #67                 // Method java/lang/invoke/MethodHandle.invoke:(Z)Ljava/lang/Integer;
// (Number)mh.bindTo(new MHSuper()).invoke(false);
invokevirtual #70                 // Method java/lang/invoke/MethodHandle.invoke:(Z)Ljava/lang/Number;

很容易发现,不管源码长什么样子,Method反射调用全部都是相同的签名;而MethodHandle则会根据源码形参类型生成不同的字节码(符号表),相当于在.class文件中携带了更多信息。容易想到,生成这些特化字节码需要编译器javac支持,而javac则是根据注解 @java.lang.invoke.MethodHandle.PolymorphicSignature 做代码生成。文档中描述如下:

In source code, a call to a signature polymorphic method will compile, regardless of the requested symbolic type descriptor. As usual, the Java compiler emits an invokevirtual instruction with the given symbolic type descriptor against the named method. The unusual part is that the symbolic type descriptor is derived from the actual argument and return types, not from the method declaration.

简单来说就是调用标记了PolymorphicSignature的方法时,不管源码传什么参数都是可以编译通过的,编译器其实不按源码中描述的方法签名生成字节码,而是参考实际传入参数的形式类型(或者称为变量类型更合适)生成。

在MethodHandle的文档描述中还有一点值得关注:MethodHandle的访问性检查只在创建时检查一次,而Method则是每次调用都检查。

Unlike with the Core Reflection API, where access is checked every time a reflective method is invoked, method handle access checking is performed when the method handle is created. In the case of ldc (see below), access checking is performed as part of linking the constant pool entry underlying the constant method handle.Thus, handles to non-public methods, or to methods in non-public classes, should generally be kept secret. They should not be passed to untrusted code unless their use from the untrusted code would be harmless.

也就是说,使用MethodHadle时更像平时写Java代码,只有public成员是可以访问的,protected、private的成员只能在类内部代码才能用MethodHandles工厂方法访问,外部是无法生成MethodHandle对象的(但自己创建然后泄漏出去就不怪JDK了)。而Method则是全局都能访问,比如我们惯用的技巧就是使用反射获取 sun.misc.Unsafe 引用。

综上,MethodHandle更像是在Java语法规则内手写字节码:自己创建方法签名(MethodType),自己决定调用方式(invokestatic/invokespecial/invokevirtual),自己注意访问控制(public/package/protected/private),最后还要自己决定类型隐式转换;而Method权限则大得多。虽然MethodHandle能力相对受限,不过性能确高了很多(两者的实现对比解析待续…)。

原创声明,本文系作者授权云+社区-专栏发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Android知识点总结

Java总结IO篇之其他IO流对象

432
来自专栏JAVA技术站

JAVA流之DataInputStream,OutInputStream

数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中。然后应用程序可以使用数据输入流将数据读入。

772
来自专栏Java编程

Java IO详解

初学Java时,一直搞不懂Java里面的io关系,在网上找了很多大多都是给个结构图草草描述也看的不是很懂。而且没有结合到java7 的最新技术,所以自己来整理一...

5780
来自专栏钟绍威的专栏

Properties+重温Map+本地计数器Map方法Properties的方法用Properties的好处

昨天想写一个记账本,发现并不能把项目名称与内容关联起来,于是乎我想到了map,可是又不知道map储存到文件中又怎么读出来,幸好今天遇到了properties ...

1827
来自专栏后端之路

捉虫记之dozer映射父类属性被重写

Dozer是我们常用的Java的拷贝工具,一般用来做属性的映射。我们通常封装如下: public class DozerHelper { priva...

2799
来自专栏Hongten

spring开发_AOP_代理模式

http://www.cnblogs.com/hongten/gallery/image/112445.html

682
来自专栏Android干货

源码浅谈(一):java中的 toString()方法

1703
来自专栏向治洪

java基础之反射

Contents java基础巩固笔记(1)-反射 反射 反射基本使用 数组的反射 配置文件加载 内省(Instropector) & JavaBean ...

1746
来自专栏一个会写诗的程序员的博客

《Kotlin极简教程》第五章 Kotlin面向对象编程(OOP)一个OOP版本的HelloWorld构造函数传参Data Class定义接口&实现之写pojo bean定一个Rectangle对象封

We frequently create a class to do nothing but hold data. In such a class some s...

754
来自专栏一直在跳坑然后爬坑

前往kotlin的路上

官网:http://kotlinlang.org/docs/reference/ github:https://github.com/JetBrains/ko...

721

扫码关注云+社区