如何使用可变参数调用 MethodHandle
How to invoke a MethodHandle with varargs
我正在尝试用 MethodHandle 替换反射调用,但可变参数似乎无法处理。
我的反射调用器目前看起来像这样:
public class Invoker {
private final Method delegate;
public Invoker(Method delegate) {
this.delegate = delegate;
}
public Object execute(Object target, Object[] args) {
return delegate.invoke(target, args);
}
}
我目前尝试重写它看起来像这样(Invoker
公开的接口必须保持不变):
public class Invoker {
private final Method delegate;
private final MethodHandle handle;
public Invoker(Method delegate) {
this.delegate = delegate;
this.handle = MethodHandles.lookup().unreflect(delegate);
}
public Object execute(Object target, Object[] args) throws InvocationTargetException, IllegalAccessException {
Object[] allArgs = Stream.concat(Stream.of(target), Stream.of(args)).toArray(Object[]::new);
return handle.invokeWithArguments(allArgs);
}
}
这在大多数情况下都可以正常工作。但是 varargs 破坏了一切。
例如。有一个像这样的方法:
public String test(int i, String... args) {
return ...;
}
还有这样的论点:
Object[] args = new Object[] {10, new String[] {"aaa", "bbb"}};
和 execute
如上实施将失败。我尝试了 asSpreader()
、MethodHandles.explicitCastArguments()
、invoke
的各种组合而不是 invokeWithArguments
等,但没有成功。
我调用可变参数方法的唯一方法是提供内联参数而不是数组。例如
handle.invokeWithArguments(10, "aaa", "bbb")
但我不能那样做,也不能保持 Invoker
当前具有的通用性质。
按照我尝试的方式真的不可能吗?
更新:
在对各种场景进行基准测试后,我决定坚持反思,因为 invokeWithArguments
在 所有 测试案例中的表现要差得多。
我完成了您的代码以重现您的问题,但它可以使用 invokeWithArguments。也许我错过了什么?
public class Main {
public String test(int i, String... args) {
return i + Stream.of(args).collect(Collectors.joining());
}
public static void main(String[] args) throws Throwable {
Main main = new Main();
Method method = Main.class.getMethod(
"test",
int.class,
String[].class)
Invoker invoker = new Invoker(method);
assertEquals("1foobar", invoker.execute(main, new Object[]{1, "foo", "bar"})); // Success
}
public static class Invoker {
private final MethodHandle handle;
public Invoker(final Method delegate) throws Exception {
this.handle = MethodHandles.lookup().unreflect(delegate);
}
public Object execute(Object target, Object[] args) throws Throwable {
// Add the target and all arguments in a new array
Object[] allArgs = Stream.concat(Stream.of(new Object[]{target}), Stream.of(args))
.toArray(Object[]::new);
return handle.invokeWithArguments(allArgs);
}
}
}
似乎您只需要调用一次 .asFixedArity
,因为默认情况下 java 将使用 asVarargsCollector
创建方法句柄
public class Main {
public static String test(int i, String... args) { return "works!"; }
public static void main(String[] args) throws Throwable {
Method method = Main.class.getMethod("test", int.class, String[].class);
System.out.println(new Invoker(method).execute(null, new Object[]{1, new String[] {"foo", "bar"} }));
}
public static class Invoker {
private final MethodHandle handle;
public Invoker(final Method delegate) throws Exception {
MethodHandle handle = MethodHandles.lookup().unreflect(delegate);
if (Modifier.isStatic(delegate.getModifiers())) { // for easy static methods support
handle = MethodHandles.dropArguments(handle, 0, Object.class);
}
this.handle = handle.asFixedArity();
}
public Object execute(Object target, Object[] args) throws Throwable {
Object[] allArgs = new Object[args.length + 1];
allArgs[0] = target;
System.arraycopy(args, 0, allArgs, 1, args.length);
return handle.invokeWithArguments(allArgs);
}
}
}
还有许多其他可能的解决方案,例如您可以向 Invoker 构造函数添加更多逻辑(静态工厂可能是个好主意)并使用 asType
方法准备您想要的签名,然后您应该可以使用 .invokeExact
调用它以获得小的性能提升。
您也可以继续使用 Method
;)
我正在尝试用 MethodHandle 替换反射调用,但可变参数似乎无法处理。
我的反射调用器目前看起来像这样:
public class Invoker {
private final Method delegate;
public Invoker(Method delegate) {
this.delegate = delegate;
}
public Object execute(Object target, Object[] args) {
return delegate.invoke(target, args);
}
}
我目前尝试重写它看起来像这样(Invoker
公开的接口必须保持不变):
public class Invoker {
private final Method delegate;
private final MethodHandle handle;
public Invoker(Method delegate) {
this.delegate = delegate;
this.handle = MethodHandles.lookup().unreflect(delegate);
}
public Object execute(Object target, Object[] args) throws InvocationTargetException, IllegalAccessException {
Object[] allArgs = Stream.concat(Stream.of(target), Stream.of(args)).toArray(Object[]::new);
return handle.invokeWithArguments(allArgs);
}
}
这在大多数情况下都可以正常工作。但是 varargs 破坏了一切。 例如。有一个像这样的方法:
public String test(int i, String... args) {
return ...;
}
还有这样的论点:
Object[] args = new Object[] {10, new String[] {"aaa", "bbb"}};
和 execute
如上实施将失败。我尝试了 asSpreader()
、MethodHandles.explicitCastArguments()
、invoke
的各种组合而不是 invokeWithArguments
等,但没有成功。
我调用可变参数方法的唯一方法是提供内联参数而不是数组。例如
handle.invokeWithArguments(10, "aaa", "bbb")
但我不能那样做,也不能保持 Invoker
当前具有的通用性质。
按照我尝试的方式真的不可能吗?
更新:
在对各种场景进行基准测试后,我决定坚持反思,因为 invokeWithArguments
在 所有 测试案例中的表现要差得多。
我完成了您的代码以重现您的问题,但它可以使用 invokeWithArguments。也许我错过了什么?
public class Main {
public String test(int i, String... args) {
return i + Stream.of(args).collect(Collectors.joining());
}
public static void main(String[] args) throws Throwable {
Main main = new Main();
Method method = Main.class.getMethod(
"test",
int.class,
String[].class)
Invoker invoker = new Invoker(method);
assertEquals("1foobar", invoker.execute(main, new Object[]{1, "foo", "bar"})); // Success
}
public static class Invoker {
private final MethodHandle handle;
public Invoker(final Method delegate) throws Exception {
this.handle = MethodHandles.lookup().unreflect(delegate);
}
public Object execute(Object target, Object[] args) throws Throwable {
// Add the target and all arguments in a new array
Object[] allArgs = Stream.concat(Stream.of(new Object[]{target}), Stream.of(args))
.toArray(Object[]::new);
return handle.invokeWithArguments(allArgs);
}
}
}
似乎您只需要调用一次 .asFixedArity
,因为默认情况下 java 将使用 asVarargsCollector
public class Main {
public static String test(int i, String... args) { return "works!"; }
public static void main(String[] args) throws Throwable {
Method method = Main.class.getMethod("test", int.class, String[].class);
System.out.println(new Invoker(method).execute(null, new Object[]{1, new String[] {"foo", "bar"} }));
}
public static class Invoker {
private final MethodHandle handle;
public Invoker(final Method delegate) throws Exception {
MethodHandle handle = MethodHandles.lookup().unreflect(delegate);
if (Modifier.isStatic(delegate.getModifiers())) { // for easy static methods support
handle = MethodHandles.dropArguments(handle, 0, Object.class);
}
this.handle = handle.asFixedArity();
}
public Object execute(Object target, Object[] args) throws Throwable {
Object[] allArgs = new Object[args.length + 1];
allArgs[0] = target;
System.arraycopy(args, 0, allArgs, 1, args.length);
return handle.invokeWithArguments(allArgs);
}
}
}
还有许多其他可能的解决方案,例如您可以向 Invoker 构造函数添加更多逻辑(静态工厂可能是个好主意)并使用 asType
方法准备您想要的签名,然后您应该可以使用 .invokeExact
调用它以获得小的性能提升。
您也可以继续使用 Method
;)