前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >理解 JDK 中的 MethodHandle

理解 JDK 中的 MethodHandle

原创
作者头像
serena
修改2021-08-03 14:56:09
5K0
修改2021-08-03 14:56:09
举报
文章被收录于专栏:社区的朋友们社区的朋友们

作者:梁德泉

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能力相对受限,不过性能确高了很多(两者的实现对比解析待续…)。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档