Java中MethodHandle的使用问题?

软件工程师、主攻高级编程语言虚拟机的设计与实现

119 👍 / 23 💬

问题描述

class Test {

class GrandFather {
    void thinking() {
        System.out.println("i am grandfather");
    }
}

class Father extends GrandFather {
    void thinking() {
        System.out.println("i am father");
    }
}

class Son extends Father {
     void thinking() {
          try {
                MethodType mt = MethodType.methodType(void.class);
                MethodHandle mh = lookup().findSpecial(GrandFather.class, 
"thinking", mt, getClass());
                mh.invoke(this);
            } catch (Throwable e) {
            }
        }
    }

    public static void main(String[] args) {
        (new Test().new Son()).thinking();
    }
}

以上代码为什么输出会是“i am father”,书上说是“i am grandfather”

题主参考的是哪本书?请指出具体书名、版本和页码。

您自己实验得到的结果是正确的,而您参考的书所说的是错误的。

MethodHandle用于模拟invokespecial时,必须遵守跟Java字节码里的invokespecial指令相同的限制——它只能调用到传给findSpecial()方法的最后一个参数(“specialCaller”)的直接父类的版本。invokespecial指令的规定可以参考JVM规范:

Chapter 6. The Java Virtual Machine Instruction Set

,不过这部分写得比较“递归”所以不太直观。

findSpecial()还特别限制如果Lookup发现传入的最后一个参数(“specialCaller”)跟当前类不一致的话默认会马上抛异常:

jdk8u/jdk8u/jdk: e2117e30fb39 src/share/classes/java/lang/invoke/MethodHandles.java

在这个例子里,Son <: Father <: GrandFather,而Father与GrandFather类上都有自己的thinking()方法的实现,因而从Son出发查找就会找到其直接父类Father上的thinking(),即便传给findSpecial()的第一个参数是GrandFather。

请参考文档:

MethodHandles.Lookup (Java Platform SE 8 )

-

题主所参考的书给的例子不正确,可能是因为findSpecial()得到的MethodHandle的具体语义在JSR 292的设计过程中有被调整过。有一段时间findSpecial()得到的MethodHandle确实可以超越invokespecial的限制去调用到任意版本的虚方法,但这种行为很快就被认为是bug而修正了。

利益相关:参与过Oracle JDK的JSR 292的实现,JDK类库部分和VM部分都有参与。也参与了Azul Systems的Zing JVM的JSR 292的JIT编译器部分的实现。