使用 java MethodHandles 实现鸭子类型
Implement duck typing using java MethodHandles
我有两个 classes A
和 B
,它们都定义了具有共同签名的方法 foo()
(什么都不接受,return void)。它们没有声明此方法的公共基 class(或接口)。我想在 As 或 Bs 上调用此方法,只要他们可以响应此调用即可。这种方法称为 Duck Typing.
我知道有一条指令叫 invokedynamic:
Each instance of an invokedynamic instruction is called a dynamic call
site. A dynamic call site is originally in an unlinked state, which
means that there is no method specified for the call site to invoke.
As previously mentioned, a dynamic call site is linked to a method by
means of a bootstrap method. A dynamic call site's bootstrap method is
a method specified by the compiler for the dynamically-typed language
that is called once by the JVM to link the site. The object returned
from the bootstrap method permanently determines the call site's
behavior.
所以我尝试使用 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 中的注释定义基础 class,因此我想实现此鸭子类型,而不是 instanceof 和 class 强制转换链或使用违反访问权限的反射检索这些值如果可能的话。
谢谢。
当 VM 第一次遇到 invokedynamic
指令时,它会调用工厂方法或 'bootstrap' 方法,returns 目标实现的 CallSite
对象实际功能。您可以使用 MutableCallSite
自己实现它,它在第一次调用时查找您的目标方法,然后将它自己的目标设置为查找的方法。
但是,这对您的目的来说还不够。当你遇到新的接收器类型时,你想重新link调用站点。
这里有一个例子(目前只支持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
方法,该方法将查找目标方法然后调用它,以及重新 linking 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");
}
}
我有两个 classes A
和 B
,它们都定义了具有共同签名的方法 foo()
(什么都不接受,return void)。它们没有声明此方法的公共基 class(或接口)。我想在 As 或 Bs 上调用此方法,只要他们可以响应此调用即可。这种方法称为 Duck Typing.
我知道有一条指令叫 invokedynamic:
Each instance of an invokedynamic instruction is called a dynamic call site. A dynamic call site is originally in an unlinked state, which means that there is no method specified for the call site to invoke. As previously mentioned, a dynamic call site is linked to a method by means of a bootstrap method. A dynamic call site's bootstrap method is a method specified by the compiler for the dynamically-typed language that is called once by the JVM to link the site. The object returned from the bootstrap method permanently determines the call site's behavior.
所以我尝试使用 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 中的注释定义基础 class,因此我想实现此鸭子类型,而不是 instanceof 和 class 强制转换链或使用违反访问权限的反射检索这些值如果可能的话。
谢谢。
当 VM 第一次遇到 invokedynamic
指令时,它会调用工厂方法或 'bootstrap' 方法,returns 目标实现的 CallSite
对象实际功能。您可以使用 MutableCallSite
自己实现它,它在第一次调用时查找您的目标方法,然后将它自己的目标设置为查找的方法。
但是,这对您的目的来说还不够。当你遇到新的接收器类型时,你想重新link调用站点。
这里有一个例子(目前只支持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
方法,该方法将查找目标方法然后调用它,以及重新 linking 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");
}
}