《深入理解Java虚拟机》JVM是怎么实现方法的动态调用的?方法句柄
1.方法句柄出现的原因
某个国家举办了跑步比赛,有亚洲,欧洲还是非洲人参赛,但是有机器人也参赛了。机器人不属于人类阵营,怎么能让机器人也参加进来呢?
interface Human{
void race();
}
class Asian implements Human{
public void race(){
System.out.println("asian running");
} ;
}
class European implements Human{
public void race(){
System.out.println("european running");
}
}
class African implements Human{
public void race(){
System.out.println("african running");
}
}
class Robot{
public void race(){
System.out.println("robot running");
}
}
通常我们的做法是创建一个包装类,将Robot的race方法也包装一下,如下:
class SpecialHuman implements Human{
private Robot robot;
@Override
public void race() {
robot.race();
}
}
或者可以利用反射,去调用每个类中的race方法
方式一:调用方式
//方式一:包装
Asian asian = new Asian();
European european = new European();
African african = new African();
//参赛选手集合
ArrayList<Human> humans = new ArrayList<>();
humans.add(african);
humans.add(european);
humans.add(asian);
Robot robot = new Robot();
SpecialHuman specialHuman = new SpecialHuman();
specialHuman.setRobot(robot);
humans.add(specialHuman);
for (Human human : humans) {
human.race();
}
方式二:反射调用方法
//方法二
Asian asian = new Asian();
European european = new European();
African african = new African();
Robot robot = new Robot();
ArrayList<Object> objectArrayList = new ArrayList<>();
objectArrayList.add(african);
objectArrayList.add(european);
objectArrayList.add(asian);
objectArrayList.add(robot);
for (Object o : objectArrayList) {
Class<?> aClass = o.getClass();
Method race = aClass.getMethod("race");
race.invoke(o);
}
比起直接调用,这两种方式都比较复杂,性能更加低效。为了解决这个问题,从Java7开始,引入了动态调用方法的invokedynamic。该指令的调用机制抽象出调用点这一个概念,并允许应用程序将调用点链接至任意符合条件的方法上。
作为 invokedynamic 的准备工作,Java 7 引入了更加底层、更加灵活的方法抽象 :方法句柄(MethodHandle)。
2.什么是方法句柄
方法句柄是一个强类型的,能够被直接执行的引用[2]。该引用可以指向常规的静态方法或者实例方法,也可以指向构造器或者字段。当指向字段时,方法句柄实则指向包含字段访问字节码的虚构方法,语义上等价于目标字段的 getter 或者 setter 方法,注意是语义上,并不是实际等于,可能实际上某个字段的get方法并不不是获取字段本身的值。
方法句柄 由 方法的形参列表和返回值类型进行匹配,与方法名和类名无关。
方法句柄的访问权限由LookUp的创建位置决定,与句柄的创建位置无关。在执行时不会被权限修饰符限制。如下的代码,LookUp创建在一个静态的public的方法中,因此Foo类相当于裸奔了,可以随时随地的调用bar方法,并且无需开启暴力反射
class Foo {
private static void bar(Object o) {
..
}
public static Lookup lookup() {
return 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);
3.方法句柄的操作
句柄的调用
严格匹配参数类型的 invokeExact
方法句柄的调用可分为两种,一是需要严格匹配参数类型的 invokeExact。它有多严格呢?假设一个方法句柄将接收一个 Object 类型的参数,如果你直接传入 String 作为实际参数,那么方法句柄的调用会在运行时抛出方法类型不匹配的异常。正确的调用方式是将该 String 显式转化为 Object 类型。
比如还是上述的代码,我们把invoke改成invokeExact方法,执行就会报错,会告诉你参数期望类型不一致
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
class Foo {
private static void bar(Object o) {
System.out.println("bar 执行了");
}
public static MethodHandles.Lookup lookup() {
return MethodHandles.lookup();
}
}
class Main2{
public static void main(String[] args) throws Throwable {
//获取方法对象
Class<Foo> fooClass = Foo.class;
Method bar = fooClass.getDeclaredMethod("bar", Object.class);
//获取方法句柄
MethodHandles.Lookup lookup = Foo.lookup();
MethodHandle unreflect = lookup.unreflect(bar);
unreflect.invokeExact(new String());
}
}
跟进方法会发现此本地方法的方法声明上有个@PolymorphicSignature注解。在碰到被它注解的方法调用时,Java 编译器会根据所传入参数的声明类型来生成方法描述符,而不是采用目标方法所声明的描述符。
如下代码的字节码所示
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
class Foo {
private static void bar(Object o) {
System.out.println("bar 执行了");
}
public static MethodHandles.Lookup lookup() {
return MethodHandles.lookup();
}
}
class Main2{
public static void main(String[] args) throws Throwable {
//获取方法对象
Class<Foo> fooClass = Foo.class;
Method bar = fooClass.getDeclaredMethod("bar", Object.class);
//获取方法句柄
MethodHandles.Lookup lookup = Foo.lookup();
MethodHandle unreflect = lookup.unreflect(bar);
unreflect.invokeExact(new String());
unreflect.invokeExact(new String());
}
}
0 ldc #2 <Foo>
2 astore_1
3 aload_1
4 ldc #3 <bar>
6 iconst_1
7 anewarray #4 <java/lang/Class>
10 dup
11 iconst_0
12 ldc #5 <java/lang/Object>
14 aastore
15 invokevirtual #6 <java/lang/Class.getDeclaredMethod : (Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;>
18 astore_2
19 invokestatic #7 <Foo.lookup : ()Ljava/lang/invoke/MethodHandles$Lookup;>
22 astore_3
23 aload_3
24 aload_2
25 invokevirtual #8 <java/lang/invoke/MethodHandles$Lookup.unreflect : (Ljava/lang/reflect/Method;)Ljava/lang/invoke/MethodHandle;>
28 astore 4
30 aload 4
32 new #9 <java/lang/String>
35 dup
36 invokespecial #10 <java/lang/String.<init> : ()V>
39 invokevirtual #11 <java/lang/invoke/MethodHandle.invokeExact : (Ljava/lang/String;)V>
42 aload 4
44 new #5 <java/lang/Object>
47 dup
48 invokespecial #1 <java/lang/Object.<init> : ()V>
51 invokevirtual #12 <java/lang/invoke/MethodHandle.invokeExact : (Ljava/lang/Object;)V>
54 return
同时我们也能发现,方法句柄调用的指令是 invokevirtual,其实这个指令与原方法调用指令是一致的,此处是调用实列方法,因此为 invokevirtual
自动适配参数类型的 invoke
如果你需要自动适配参数类型,那么你可以选取方法句柄的第二种调用方式 invoke。它同样是一个签名多态性的方法。invoke 会调用 MethodHandle.asType 方法,生成一个适配器方法句柄,对传入的参数进行适配,再调用原方法句柄。调用原方法句柄的返回值同样也会先进行适配,然后再返回给调用者。
4.方法句柄调用目标方法
改写第一个比赛跑步代码
import jdk.nashorn.internal.runtime.Specialization;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Objects;
interface Human {
void race();
}
class Asian implements Human {
public void race() {
System.out.println("asian running");
};
public static MethodHandles.Lookup lookup() {
return MethodHandles.lookup();
}
}
class European implements Human {
public void race() {
System.out.println("european running");
}
public static MethodHandles.Lookup lookup() {
return MethodHandles.lookup();
}
}
class African implements Human {
public void race() {
System.out.println("african running");
}
public static MethodHandles.Lookup lookup() {
return MethodHandles.lookup();
}
}
class Robot {
public void race() {
System.out.println("robot running");
}
public static MethodHandles.Lookup lookup() {
return MethodHandles.lookup();
}
}
class SpecialHuman implements Human {
private Robot robot;
public Robot getRobot() {
return robot;
}
public void setRobot(Robot robot) {
this.robot = robot;
}
@Override
public void race() {
robot.race();
}
}
public class RunningRace {
public static void main(String[] args) throws Throwable {
//拿到所有的Lookup,创建方法句柄
MethodHandles.Lookup lookup1 = Asian.lookup();
MethodHandles.Lookup lookup2 = European.lookup();
MethodHandles.Lookup lookup3 = African.lookup();
MethodHandles.Lookup lookup4 = Robot.lookup();
Asian asian = new Asian();
European european = new European();
African african = new African();
Robot robot = new Robot();
lookup1.findVirtual(Asian.class, "race", MethodType.methodType(void.class)).invoke(asian);
lookup2.findVirtual(European.class,"race", MethodType.methodType(void.class)).invoke(european);
lookup3.findVirtual(African.class,"race", MethodType.methodType(void.class)).invoke(african);
lookup4.findVirtual(Robot.class,"race", MethodType.methodType(void.class)).invoke(robot);
}
}