Создать BiConsumer из LambdaMetafactory - PullRequest
0 голосов
/ 04 мая 2020

Я пытаюсь динамически создать ссылку на метод типа BiConsumer через LambdaMetafactory. Я пытался применить два подхода, найденные на https://www.cuba-platform.com/blog/think-twice-before-using-reflection/ - createVoidHandlerLambda и здесь Создать BiConsumer как установщик поля без отражения ответ Хольгера.

Однако в обоих случаях у меня возникает ошибка ниже:

Exception in thread "main" java.lang.AbstractMethodError: Receiver class org.home.ref.App$$Lambda$15/0x0000000800066040 does not define or inherit an implementation of the resolved method abstract accept(Ljava/lang/Object;Ljava/lang/Object;)V of interface java.util.function.BiConsumer.
    at org.home.ref.App.main(App.java:20)

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

public class App {

    public static void main(String[] args) throws Throwable {
        MyClass myClass = new MyClass();
        BiConsumer<MyClass, Boolean> setValid = MyClass::setValid;
        setValid.accept(myClass, true);

        BiConsumer<MyClass, Boolean> mappingMethodReferences = createHandlerLambda(MyClass.class);
        mappingMethodReferences.accept(myClass, true);
    }

    @SuppressWarnings("unchecked")
    public static BiConsumer<MyClass, Boolean> createHandlerLambda(Class<?> classType) throws Throwable {
        Method method = classType.getMethod("setValid", boolean.class);
        MethodHandles.Lookup caller = MethodHandles.lookup();
        CallSite site = LambdaMetafactory.metafactory(caller,
                "accept",
                MethodType.methodType(BiConsumer.class),
                MethodType.methodType(void.class, MyClass.class, boolean.class),
                caller.findVirtual(classType, method.getName(),
                        MethodType.methodType(void.class, method.getParameterTypes()[0])),
                MethodType.methodType(void.class, classType, method.getParameterTypes()[0]));

        MethodHandle factory = site.getTarget();
        return (BiConsumer<MyClass, Boolean>) factory.invoke();
    }

    public static <C, V> BiConsumer<C, V> createSetter(Class<?> classType) throws Throwable {
        Field field = classType.getDeclaredField("valid");
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        final MethodHandle setter = lookup.unreflectSetter(field);
        final CallSite site = LambdaMetafactory.metafactory(lookup,
                "accept", MethodType.methodType(BiConsumer.class, MethodHandle.class),
                setter.type().erase(), MethodHandles.exactInvoker(setter.type()), setter.type());
        return (BiConsumer<C, V>)site.getTarget().invokeExact(setter);
    }

}

Где MyClass выглядит так:

public class MyClass {

    public boolean valid;

    public void setValid(boolean valid) {
        this.valid = valid;
        System.out.println("Called setValid");
    }
}

Я буду признателен за помощь с этим.

РЕДАКТИРОВАТЬ # 1. После консультации с @Holger я изменил метод createSetter следующим образом:

@SuppressWarnings("unchecked")
    public static <C, V> BiConsumer<C, V> createSetter(Class<?> classType) throws Throwable {
        Field field = classType.getDeclaredField("valid");
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        final MethodHandle setter = lookup.unreflectSetter(field);
        MethodType type = setter.type();
        if(field.getType().isPrimitive())
            type = type.wrap().changeReturnType(void.class);
        final CallSite site = LambdaMetafactory.metafactory(lookup,
                "accept", MethodType.methodType(BiConsumer.class, MethodHandle.class),
                type.erase(), MethodHandles.exactInvoker(setter.type()), type);
        return (BiConsumer<C, V>)site.getTarget().invokeExact(setter);
    }

Теперь этот метод не выдает первоначальное исключение, хотя кажется, что вызов метода accept для ссылки на этот метод не имеет никакого эффекта. Я не вижу "Called setValid" в журналах для этого вызова. Только для MyClass :: setValid;

1 Ответ

3 голосов
/ 04 мая 2020

Обратите внимание, что использование getMethod и caller.findVirtual(…) для одного и того же метода является излишним. Если ваша отправная точка - Method, вы можете использовать unreflect, например

Method method = classType.getMethod("setValid", boolean.class);
MethodHandles.Lookup caller = MethodHandles.lookup();
MethodHandle target = caller.unreflect(method);

. Это может быть полезно, когда вы обнаруживаете методы динамически и / или ищете другие артефакты, такие как аннотации в процессе. В противном случае достаточно просто получить MethodHandle через findVirtual.

Затем вы должны понимать три различных типа функций:

  • У дескриптора целевого метода есть специфика c тип, который дается неявно при передаче дескриптора метода на завод. В вашем случае это (MyClass,boolean) → void
  • Тип функции generi c, связанный с предполагаемым типом результата
    BiConsumer<MyClass, Boolean>, который равен (MyClass,Boolean) → void
  • Стираемый тип интерфейс BiConsumer, который является (Object,Object) → void

Только правильное указание всех трех типов говорит фабрике, что он должен реализовать метод
void accept(Object,Object) с кодом, который приведёт первый аргумент к MyClass и второй Boolean, после чего разверните второй аргумент в boolean, чтобы в конечном итоге вызвать целевой метод.

Мы могли бы явно указать типы, но сделать код повторно используемым, так как возможно, мы можем вызвать type() для цели, а затем использовать методы адаптера.

  • wrap() преобразует все примитивные типы в их типы-оболочки. К сожалению, это также подразумевает преобразование возвращаемого типа в Void, поэтому мы должны снова установить его в void.
    Это дает нам параметр instantiatedMethodType . (Сравните с документацией )
  • erase() преобразует все ссылочные типы в Object, но оставит все примитивные типы как есть. Поэтому применение его к instantiatedMethodType дает нам стертый тип.
    Это зависит от конкретного целевого интерфейса, достаточно ли этого простого преобразования. Для интерфейсов в java.util.function это.

Еще один момент для повышения возможности повторного использования - это использование фактического параметра типа для класса приемника метода, так как мы в любом случае получаем класс в качестве параметра:

public static <T>
       BiConsumer<T, Boolean> createHandlerLambda(Class<T> classType) throws Throwable {

    MethodHandles.Lookup caller = MethodHandles.lookup();
    MethodHandle target = caller.findVirtual(classType, "setValid",
        MethodType.methodType(void.class, boolean.class));
    MethodType instantiated = target.type().wrap().changeReturnType(void.class);

    CallSite site = LambdaMetafactory.metafactory(caller,
            "accept", MethodType.methodType(BiConsumer.class),
            instantiated.erase(), target, instantiated);
    return (BiConsumer<T, Boolean>)site.getTarget().invoke();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...