我有两个班 A
和 B
,都定义了方法 foo()
具有共同的签名(accept nothing, return void).它们没有共同的基类(或接口)来声明这个方法。他们没有共同的基类(或接口)来声明这个方法。我想在不管As还是Bs上调用这个方法,只要它们能响应这个调用。这种方法被称为 鸭子打字.
我知道有一个指令叫 被调用的动态:
每一个被调用的动态指令的实例称为一个动态调用站点。一个动态调用站点最初处于未链接状态,这意味着没有为调用站点指定要调用的方法。 如前所述,动态调用站点是通过引导方法与方法链接的。一个动态调用站点的引导方法是由动态类型语言的编译器指定的方法,该方法被JVM调用一次来链接站点。从bootstrap方法返回的对象永久地决定了调用站点的行为。
所以我试着用 MethodHandles. 这就是例子。
public static class A {
public void foo() {
}
}
public static class B {
public void foo() {
}
}
public static void main(String[] args) throws Throwable {
final MethodHandle foo = MethodHandles.lookup()
.findVirtual(A.class, "foo", MethodType.methodType(void.class));
foo.invoke(new B());
}
当然,我已经得到了。
Exception in thread "main" java.lang.ClassCastException: Cannot cast Main$B to Main$A
at sun.invoke.util.ValueConversions.newClassCastException(ValueConversions.java:461)
at sun.invoke.util.ValueConversions.castReference(ValueConversions.java:456)
at Main.main(Main.java:30)
我清楚地看到了两者的区别 invokedynamic
和 MethodHanle
. 我看问题是 foo
MethodHandle被绑定到 class A
,不 class B
. 但我是否有可能以某种方式利用... invokedynamic
在这种特殊情况下?
为什么我需要这个? 这是我的小型研究项目的一部分。我试图深入理解方法句柄,我想在从字段和方法中检索的注解实例上调用通用方法。我无法在Java中为注解定义基类,所以我想尽可能实现这种鸭式类型化,而不是用instanceof's链和类的casts或使用反射检索这些值侵犯访问权。
谢谢。
当虚拟机遇到一个 invokedynamic
指令,它第一次调用工厂方法,或 "bootstrap "方法,该方法返回一个 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,由类型检查来保护。
在第一次调用后,这基本上会创建一个ifelse这样的。
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");
}
}