理解 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 条评论
登录 后参与评论

相关文章

来自专栏Java Edge

遨游springmvc之HandlerExceptionResolver1.前言2.原理4.总结

3685
来自专栏编程之路

羊皮书APP (Android版)开发系列(二)日志工具类

在App开发过程中,很重要的一个调试工具就是日志的打印,Android系统自带的日志打印文件,看起来并不是很直观。这里我们自己对原生Android 日志做一个...

761
来自专栏java学习

面试题28( 关于Float,下列说法错误的是?)

关于Float,下列说法错误的是()? A Float是一个类 B Float在java.lang包中 C Float a=1.0是正确的赋值方法 D Flo...

3394
来自专栏精讲JAVA

深入 Spring Boot :怎样排查 java.lang.ArrayStoreException

这个demo来说明怎样排查一个spring boot 1应用升级到spring boot 2时可能出现的java.lang.ArrayStoreExceptio...

1184
来自专栏Java学习网

Java实现解析IP地址的方法,给出一串数字,生成正确的IP地址

给定一个只包含数字的字符串,通过方法返回所有可能的有效的IP地址组合。 例如:给出“25525511135”,返回(“255.255.11.135”、“255....

2749
来自专栏大大的微笑

JAVA中使用Jedis操作Redis

redis安装看这里:https://my.oschina.net/u/2486137/blog/1541190 需要的jar:commons-pool2 ,r...

5877
来自专栏Java帮帮-微信公众号-技术文章全总结

JavaWeb07-JDBC(Java真正的全栈开发)

? jdbc 一、JDBC介绍 1. JDBC定义 JDBC(Java Data Base Connectivity,java数据库连接),说白了就是用Jav...

3526
来自专栏绿巨人专栏

Scala underscore的用途

2685
来自专栏osc同步分享

springMVC

springmvc中有专用于页面跳转的controller,不会对请求做任何处理,直接跳转页面:     <!-- 对处理请求的controller进行映射 -...

1944
来自专栏余林丰

MyBatis之简单了解Plugin

MyBatis的Configuration配置中有一个Plugin配置,根据其名可以解释为“插件”,这个插件实质可以理解为“拦截器”。“拦截器”这个名词不陌生,...

1939

扫码关注云+社区