利用方法句柄,可以模拟其他语言的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方法调用可以内联