How to invoke a MethodHandle with varargs

↘锁芯ラ 提交于 2019-12-10 14:44:12

问题


I'm trying to replace a reflective invocation with a MethodHandle, but varargs seem to be impossible to deal with.

My reflective invoker currently looks like this:

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);
    }
}

My current attempt at rewriting it looks like this (the interface the Invoker exposes has to stay the same):

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);
    }
}

And this works just fine in most cases. But varargs break everything. E.g. have a method like:

public String test(int i, String... args) {
    return ...;
}

And the arguments like:

Object[] args = new Object[] {10, new String[] {"aaa", "bbb"}};

And execute as implemented above will fail. I tried various combinations of asSpreader(), MethodHandles.explicitCastArguments(), invoke instead of invokeWithArguments etc with no success.

The only way I can invoke a varargs method is to provide the arguments inline and not as an array. E.g.

handle.invokeWithArguments(10, "aaa", "bbb")

but I can not do that and maintain the generic nature of the Invoker that it currently has.

Is this really impossible to do the way I'm trying?

UPDATE: After benchmarking various scenarios, I decided to stick to reflection as invokeWithArguments performs significantly worse in all tested cases.


回答1:


Seems that all you need is one call to .asFixedArity as by default java will create method handle with 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);
        }
    }
}

There is also many other possible solutions, like you can add more logic to Invoker constructor (static factory might be a good idea) and use asType method to prepare signature you want and then you should be able to call this using .invokeExact to get small performance boost.

You can also just keep using Method ;)




回答2:


I completed your code to reproduce your issue, but it works using invokeWithArguments. Maybe I missed something ?

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);
        }
    }
}


来源:https://stackoverflow.com/questions/52093694/how-to-invoke-a-methodhandle-with-varargs

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!