Как вызвать MethodHandle с помощью varargs - PullRequest
0 голосов
/ 30 августа 2018

Я пытаюсь заменить отражающий вызов на MethodHandle, но с varargs, похоже, невозможно справиться.

Мой отражатель на данный момент выглядит так:

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

И это прекрасно работает в большинстве случаев. Но варагги все ломают. Например. есть метод как:

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

А аргументы вроде:

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

И execute, как реализовано выше, потерпит неудачу. Я пробовал различные комбинации asSpreader(), MethodHandles.explicitCastArguments(), invoke вместо invokeWithArguments и т. Д., Но безуспешно.

Единственный способ, которым я могу вызвать метод varargs, - предоставить аргументы inline, а не как массив. Э.Г.

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

но я не могу этого сделать и поддерживать общий характер Invoker, который у него есть в настоящее время.

Неужели это невозможно сделать так, как я пытаюсь?

UPDATE: После сравнительного анализа различных сценариев я решил придерживаться рефлексии, так как invokeWithArguments работает значительно хуже во всех тестированных случаях.

Ответы [ 2 ]

0 голосов
/ 30 августа 2018

Кажется, что все, что вам нужно, это один вызов .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;)

0 голосов
/ 30 августа 2018

Я выполнил ваш код для воспроизведения вашей проблемы, но он работает с использованием 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);
        }
    }
}
...