当虚拟机第一次遇到invokedynamic
指令时,它调用工厂方法或“引导”方法,该方法返回一个CallSite
目标实现实际功能的对象。您可以使用MutableCallSite
在第一次调用时查找您的目标方法然后将其自己的目标设置为查找的方法来实现它自己。
但是,这还不足以满足您的目的。当您遇到新的接收方类型时,您希望重新链接呼叫站点。
这是一个示例(目前仅支持findVirtual
):
class DuckTypingCallSite extends MutableCallSite {
private static final MethodHandle MH_relink;
private static final MethodHandle MH_isInstance;
static {
try {
MH_relink = lookup().findVirtual(DuckTypingCallSite.class, "link", methodType(Object.class, Object[].class));
MH_isInstance = lookup().findVirtual(Class.class, "isInstance", methodType(boolean.class, Object.class));
} catch (ReflectiveOperationException e) {
throw new InternalError(e);
}
}
private final MethodHandles.Lookup lookup;
private final String methodName;
private final MethodType lookupType;
private DuckTypingCallSite(MethodHandles.Lookup lookup, String methodName, MethodType lookupType) {
super(lookupType.insertParameterTypes(0, Object.class)); // insert receiver
this.lookup = lookup;
this.methodName = methodName;
this.lookupType = lookupType;
}
public static DuckTypingCallSite make(MethodHandles.Lookup lookup, String methodName, MethodType lookupType) {
DuckTypingCallSite cs = new DuckTypingCallSite(lookup, methodName, lookupType);
cs.setTarget(MH_relink.bindTo(cs).asCollector(Object[].class, cs.type().parameterCount()).asType(cs.type()));
return cs;
}
public Object link(Object[] args) throws Throwable {
Object receiver = args[0];
Class<?> holder = receiver.getClass();
MethodHandle target = lookup.findVirtual(holder, methodName, lookupType).asType(type());
MethodHandle test = MH_isInstance.bindTo(holder);
MethodHandle newTarget = guardWithTest(test, target, getTarget());
setTarget(newTarget);
return target.invokeWithArguments(args);
}
}
在第一次调用之前,调用调用站点的动态调用程序将直接跳转到link
方法中,该方法将查找目标方法然后调用它,以及重新链接 DuckTypingCallSite 以基本上缓存查找到的 MethodHandle,由类型检查保护.
在第一次调用之后,这实际上创建了一个 if/else,如下所示:
if (A.class.isInstance(receiver)) {
// invoke A.foo
} else {
// re-link
}
然后当遇到第二种类型时,它会变为:
if (B.class.isInstance(receiver)) {
// invoke B.foo
} else if (A.class.isInstance(receiver)) {
// invoke A.foo
} else {
// re-link
}
等等
这是一个示例用法:
public class DuckTyping {
private static final MethodHandle MH_foo = DuckTypingCallSite.make(lookup(), "foo", methodType(void.class)).dynamicInvoker();
private static void foo(Object receiver) {
try {
MH_foo.invokeExact(receiver);
} catch (Throwable throwable) {
throw new IllegalStateException(throwable);
}
}
public static void main(String[] args) {
foo(new A()); // prints "A.foo"
foo(new B()); // prints "B.foo"
}
}
class A {
public void foo() {
System.out.println("A.foo");
}
}
class B {
public void foo() {
System.out.println("B.foo");
}
}