转载请注明链接: https://blog.csdn.net/feather_wch/article/details/82719313
invokedynamic指令到底是什么?方法句柄又是什么?Java8是如何利用invokedynamic实现Lambda表达式的?
本文对invokedynamic的知识点进行归纳总结。
JVM invokedynamic调用指令
版本号:2018/09/16-1(0:00)
基础-方法句柄(32)
赛马问题
1、如何让非马的类能和马一样参加赛马比赛?
class Horse{
public void race(){
System.out.println("马在赛跑");
}
}
class Duck{
public void race(){
System.out.println("鸭子在赛跑");
}
}
class Deer{
public void race(){
System.out.println("马在赛跑");
}
}
- 第一种方法:将非马的类型包装成马类进行赛跑。
- 第二种方法:通过反射机制,查找并调用各个类型中的赛跑方法。
2、invokedynamic指令的作用?
- Java7引入的新指令
- 该指令抽象出
调用点
这一个概念- 允许应用程序将调用点链接至任何符合条件的方法上
- 解决了赛马问题,但是比包装和反射更高效。
3、invokedynamic的应用场景
lambda表达式
4、invokedynamic底层机制的基石:方法句柄
方法句柄
5、方法句柄是什么?
- MethodHandle-方法句柄
- 是一种更加底层、更加灵活的方法抽象(Java7引入)
- 方法句柄是一种强类型、并且能够被直接执行的引用。
- 该引用可以指向常规的静态方法、实例方法、构造器、字段。
- 当指向字段时,方法句柄实则指向包含字段访问字节码的
虚构方法
,语义上等价于目标字段的 getter 或者 setter 方法。(但不会直接指向目标字段所在类中的 getter/setter)
6、方法句柄的类型是什么?
- MethodType-方法句柄类型
- 方法句柄类型由所指向方法的参数类型、返回类型组成的。
- 是用来 确认方法句柄是否适配的唯一关键。
- 当使用方法句柄时,并不需要关心方法句柄所指向方法的类名或者方法名
7、如果一个兔子的赛跑方法和睡觉方法的参数类型、返回类型一致,如何判断是哪一个方法?
- 兔子传递的方法句柄,将无法判断是哪一个方法。
创建
8、方法句柄的创建?
- 方法句柄的创建需要通过 MethodHandles.Lookup 类来完成。
- Lookup提供了多个API,可以使用反射的Method来查找方法句柄。
- 也可以使用类、方法名、方法句柄类型来查找。
1-Horse类:
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
public class Horse {
public void race(){
System.out.println("马在赛跑");
}
// 返回Lookup类
public static Lookup lookup(){
return MethodHandles.lookup();
}
}
2-通过反射的Method来获取方法句柄(MethodHandle)
// 1、获取到Lookup
MethodHandles.Lookup lookup = Horse.lookup();
// 2、获取到反射的Method
Method method = Horse.class.getDeclaredMethod("race", Void.class);
// 3、获取到方法句柄
MethodHandle methodHandle = lookup.unreflect(method);
3-通过类、方法名、方法句柄类型,来获取MethodHandle
// 1、获取到Lookup
MethodHandles.Lookup lookup = Horse.lookup();
// 2、方法句柄类型,第一个是返回值类型,第二个是参数类型
MethodType methodType = MethodType.methodType(Void.class, Void.class);
// 3、创建方法句柄,依次使用类、方法名、方法句柄类型。
MethodHandle methodHandle = lookup.findVirtual(Horse.class, "race", methodType);
9、Lookup有哪些创建MethodHandle的API?
- 对于invokestatic调用指令的静态方法:使用
lookup.findStatic()
- 对于invokevirutal、invokeinterface调用指令的方法:使用
lookup.findVirtual()
- 对于invokespecial调用指令的实例方法:使用
lookup.findSpecial()
10、方法句柄(MethodHandle)的权限问题?
- 权限问题上和反射API不同
- MethodHandle的权限检查是在句柄创建阶段完成
- 实际调用过程中,JVM不会检查方法句柄的权限,如果被多次调用,性能比反射要高。
- 方法句柄没有运行时检查权限,因此app需要负责方法句柄的管理。
11、方法句柄和反射的性能对比
- 方法句柄的权限检查在创建阶段完成
- 反射的权限检查是处于运行时。
- JVM不会去检查方法句柄的权限。
- 因此在多次调用的情况下,方法句柄会节省出权限检查的开销。
12、方法句柄的权限是如何判定的?
- 方法句柄的访问权限不取决于MethodHandle的创建位置
- 而是取决于Lookup对象的创建位置。
- 如果Lookup对象在private字段所在类中获取,就拥有了对该private字段的访问权限。
- 这样在所在类的外边,也可以通过该Lookup对象创建该private字段的getter或者setter。
操作
13、方法句柄的调用方法?
分为两种:
1. 需要严格匹配参数类型的invokeExact
1. 自动适配参数类型的invoke
14、invokeExact的使用
- 对匹配参数类型非常严格
- 如果MethodHandle接收一个Object类型的参数,传入String作为实参就会在运行时抛出异常
- 必须要显式转换类型,让形参和实参一致。
methodHandle.invokeExact(str);
methodHandle.invokeExact((Object)str);
15、重载方法时的显式转化类型是为了什么?
- 在显式转化后,参数的声明类型发生了改变,从而匹配到不同的方法描述符
- 根据方法描述符的不同,选取到不同的目标方法。
- 调用方法句柄也是一样的道理,并且涉及到
签名多态性-signature polymorphism
的概念
16、方法句柄API的注解@PolymorphicSignature
是什么?这个方法描述符和invokeExact的关系?
- 通过该注解,Java编译器在遇到这些方法调用时,会根据传入参数的声明类型来生成方法描述符
- 而不是采用目标方法所声明的描述符。
- invokeExact会严格要求方法句柄的类型和调用时的方法描述符是否匹配
- 如下面的例子:就是根据
传入参数的声明类型
来生成描述符
methodHandle.invokeExact(str);
// 描述符:MethodHandle.invokeExact:(Ljava/lang/String;)V
methodHandle.invokeExact((Object)str);
// 描述符: MethodHandle.invokeExact:(Ljava/lang/Object;)V
17、什么是签名多态性?
- 方法用
@PolymorphicSignature
注解进行标记- Java编译器在调用这些方法时,会根据传入参数的声明类型来生成方法描述符
18、invoke的使用和内部原理
methodHandle.invoke(str);
- 该方法自动适配参数类型,也是一个签名多态性的方法
- 调用方法句柄前,会调用
MethodHandle.asType()
生成一个适配器方法句柄,对传入的参数进行适配。- 方法句柄的返回值,也会进行适配然后返回给调用者。
19、方法句柄的增加、删除、修改参数的操作
本质都是通过生成另一个方法句柄来实现
1. 修改操作:MethodHandle.asType()
1. 删除操作: MethodHandles.dropArguments(), 将传入的部分参数直接抛弃,然后调用另一个方法句柄。
1. 增加操作:MethodHandle.bindTo(), 在传入的参数中增加额外的参数,再调用另一个方法句柄。
20、Java 8中捕获类型的Lambda表达式是如何实现的?
利用的方法句柄的增加操作来实现的。
21、MethodHandle的增加操作还可以实现方法的柯里化
- 一个指向f(x,y)的句柄,将x绑定为4,生成一个方法句柄
g(y)=f(4, y)
。- 执行过程中,调用g(y)方法句柄,就会默认去调用f(4,y)的方法句柄
22、MethodHandle.bindTo()限制了只能使用引用类型,普遍是用来绑定this的,为什么能用来实现柯里化(将int x, int y 的第一个参数指定为常数4)?
- bindTo的确限制了引用类型,但是方法句柄不区分调用者和参数。
- 因此依然可以用做其他效果:可以用Integer,然后使用静态方法,或者在使用virtual方法时将bindTo返回的方法句柄再bindTo一个Integer.valueOf(4)。
- BoundMethodHandle里有很多非public的bindArgumentXXX()方法,和这些有相关性。
方法句柄的实现
23、方法句柄的使用实例
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
public class Horse {
public static void race(){
System.out.println("马在赛跑");
}
// 返回Lookup类
public static Lookup lookup(){
return MethodHandles.lookup();
}
}
// 1、获取到Lookup
MethodHandles.Lookup lookup = Horse.lookup();
// 2、方法句柄类型,第一个是返回值类型,第二个是参数类型
MethodType methodType = MethodType.methodType(void.class);
// 3、创建方法句柄,依次使用类、方法名、方法句柄类型。
MethodHandle methodHandle = lookup.findStatic(Horse.class, "race", methodType);
// 调用
methodHandle.invoke();
// 打印出
马在赛跑
24、查看方法句柄调用invoke、invokeExact的栈轨迹
public class Horse {
public static void race(){
// 打印栈轨迹
new Exception().printStackTrace();
System.out.println("马在赛跑");
}
// xxx
}
栈信息
java.lang.Exception
at Horse