利用方法句柄,可以模拟其他语言的duckertype

而我们获取一个目标方法的时候,可以采用原本的反射去获取

或者使用方法句柄,利用invokedynamic,将调用机制抽象出调用点一个概念,并可以链接到任意符合条件的方法

invokedynamic前身则是一个强类型的,可以直接执行的引用的方法句柄

可以指向任意的方法或者字段

构成由目标方法的参数类型,返回类型组成

可以不关心指向方法的类名或者方法名

方法句柄的创建时通过MethodHandles.Lookup完成的

// 获取方法句柄的不同方式MethodHandles.Lookup l = Foo.lookup();

// 具备Foo类的访问权限Method m = Foo.class.getDeclaredMethod(“bar”, Object.class);

MethodHandle mh0 = l.unreflect(m);

MethodType t = MethodType.methodType(void.class, Object.class);

MethodHandle mh1 = l.findStatic(Foo.class, “bar”, t);

句柄可以省下权限检查的开销

句柄的使用分为两种

一种是需要严格匹配参数类型的invokeExact,如果需要是Object传入的是String都会报错

一种是不需要严格匹配的invoke,调用MethodHandle.asType,生成一个适配器方法句柄,对传入的参数进行适配,返回值也进行适配

方法句柄支持增删改操作

改就是asType,

删就是讲传入的参数抛弃,然后调用剩余参数符合的句柄,为 dropArguments

增,就是往传入的参数插入额外的参数,调用另一个方法句柄,对应的API是MethodHandle.bindTo

比如,有一个指向 f(x,y)的方法句柄,可以绑定为4,然后生成另一个方法 g(y)=f(4,y) 然后调用g(y) = f(4,y)

而具体的invokedynamic是Java7之后引入的一条新指令,支持动态语言的方法调用,

将原本Java虚拟机控制的方法调用以及方法链接暴露给了应用程序,

有一个初始化,就是第一次执行invokedynamic的时候,会调用这个指令对应的启动方法 BootStrap Method,生成前面的调用点,绑定至invokedynamic指令中,后续直接调用就行了

启动方法是一个返回类型 为调用点的静态方法,接收三个固定的参数,分别为一个Lookup类实例,指代目标方法名字的字符串,调用点链接的方法句柄的类型

public static CallSite bootstrap(MethodHandles.Lookup l, String name, MethodType callSiteType) throws Throwable {

MethodHandle mh = l.findVirtual(Horse.class, name, MethodType.methodType(void.class));

return new ConstantCallSite(mh.asType(callSiteType));

}

invokedynamic生成实现了函数式接口的适配器,包含了一个非default接口方法的接口

通过@FunctionalInterface来表示说明,即使不加,Java编译器也会自动识别符合的接口

lambda表达式也是借助invokedynamic来实现的

Java编译器利用invokedynamic指令来生成实现了函数接口的适配器

int x = ..IntStream.of(1, 2, 3).map(i -> i * 2).map(i -> i * x);

上面会有两次的映射,就是将i->i2 i->ix进行了转换,转化为了IntUnaryOperator实例,这个过程交由invokedynamic实现的

编译器会对Lambda表达式解除语法糖,生成对应的函数方法,而且会捕获外部的变量

第一个i->i2没有捕获其他变量,第二个则进行捕获了局部变量

// i -> i * 2

private static int lambda$0(int);

Code:

0: iload_0

1: iconst_2

2: imul

3: ireturn

// i -> i * x

private static int lambda$1(int, int);

Code:

0: iload_1

1: iload_0

2: imul

3: ireturn

上面那个可以返回一个固定的方法句柄,直接优化到位

下面的捕获了其他的变量,那么每次执行invokedynamic的时候,都需要更新这个变量防止发生了变化

所以没法共享一个适配器类的实例,所以需要新建一个适配器实例

所以会有一个额外的静态方法,构造适配器的实例,接收对应的捕获参数,保存为适配器类的实例字段

// i->i*2 对应的适配器类

final class LambdaTest$$Lambda$1 implements IntUnaryOperator {

private LambdaTest$$Lambda$1();

Code:

0: aload_0

1: invokespecial java/lang/Object.”<init>”:()V

4: return

public int applyAsInt(int);

Code:

0: iload_1

1: invokestatic LambdaTest.lambda$0:(I)I

4: ireturn

}

// i->i*x 对应的适配器类

final class LambdaTest$$Lambda$2 implements IntUnaryOperator {

private final int arg$1;

private LambdaTest$$Lambda$2(int);

Code:

0: aload_0

1: invokespecial java/lang/Object.”<init>”:()V

4: aload_0

5: iload_1

6: putfield arg$1:I

9: return

private static java.util.function.IntUnaryOperator get$Lambda(int);

Code:

0: new LambdaTest$$Lambda$2

3: dup

4: iload_0

5: invokespecial “<init>”:(I)V

8: areturn

public int applyAsInt(int);

Code:

0: aload_0

1: getfield arg$1:I

4: iload_1

5: invokestatic LambdaTest.lambda$1:(II)I

8: ireturn

}

捕获了局部变量的lambda表达式多了get$Lamdba的方法,会返回调用点连接至调用该方法的方法句柄,说明每次都会新生成一个适配类实例

但是新构造的适配器实例,在后续的执行过程中,会将新建实例优化掉,这是因为了逃逸分析,逃逸分析需要满足 invokedynamic指定执行的方法句柄可以内联,以及后续的accept方法调用可以内联

发表评论

邮箱地址不会被公开。 必填项已用*标注