LambdaMetaFactory с конкретной реализацией универсального типа - PullRequest
0 голосов
/ 14 октября 2018

Я пытаюсь использовать Java LambdaMetaFactory для динамической реализации общей лямбды, Handler<RoutingContext>:

public class RoutingContext {
    // ...
}

@FunctionalInterface
public interface Handler<X> {
    public void handle(X arg);
}

public class HomeHandler extends Handler<RoutingContext> {
    @Override
    public void handle(RoutingContext ctx) {
        // ...
    }
}

Вот моя попытка LambdaMetaFactory:

try {
    Class<?> homeHandlerClass = HomeHandler.class;

    Method method = homeHandlerClass.getDeclaredMethod(
            "handle", RoutingContext.class);
    Lookup lookup = MethodHandles.lookup();
    MethodHandle mh = lookup.unreflect(method);

    MethodType factoryMethodType = MethodType.methodType(Handler.class);
    MethodType functionMethodType = mh.type();
    MethodHandle implementationMethodHandle = mh;

    Handler<RoutingContext> lambda =
            (Handler<RoutingContext>) LambdaMetafactory.metafactory(
                    lookup,
                    "handle",
                    factoryMethodType, 
                    functionMethodType,
                    implementationMethodHandle,
                    implementationMethodHandle.type()) 
            .getTarget()
            .invokeExact();

    lambda.handle(ctx);

} catch (Throwable e) {
    e.printStackTrace();
}

Это выдает ошибку:

java.lang.AbstractMethodError: Receiver class [...]$$Lambda$82/0x00000008001fa840
does not define or inherit an implementation of the resolved method abstract
handle(Ljava/lang/Object;)V of interface io.vertx.core.Handler.

Я пробовал ряд других опций для functionMethodType и implementationMethodHandle, но пока не смог заставить это работать.Кроме того, даже если я заменим ссылку RoutingContext.class на Object.class, это не исправит ошибку.

Единственный способ добиться успешного вызова lambda.handle(ctx) - это изменить HomeHandler так, чтобыон не расширяется Handler, делая HomeHandler::handle статическим и изменяя RoutingContext.class на Object.class.Как ни странно, я все еще могу привести итоговую лямбду к Handler<RoutingContext>, даже если она больше не расширяется Handler.

Мои вопросы:

  1. Как мне получить LambdaMetaFactory для работы с нестатическими методами?

  2. Для этого нестатического класса SAM HomeHandler как это работает с распределением экземпляров под капотом?Создает ли LambdaMetaFactory один экземпляр реализации интерфейса, независимо от того, сколько вызовов методов, так как в этом примере нет захваченных переменных?Или это создает новый экземпляр для каждого вызова метода?Или я должен был создать один экземпляр и как-то передать его в API?

  3. Как заставить LambdaMetaFactory работать с универсальными методами?

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

https://medium.freecodecamp.org/a-faster-alternative-to-java-reflection-db6b1e48c33e

Ответы [ 2 ]

0 голосов
/ 17 октября 2018

Поскольку вы сказали, что «позор, API-интерфейс LambdaMetaFactory настолько сложен», следует отметить, что это можно сделать проще.

Во-первых, при использовании LambdaMetaFactory используйте его прямо:

Lookup lookup = MethodHandles.lookup();
MethodType fType = MethodType.methodType(void.class, RoutingContext.class);
MethodHandle mh = lookup.findVirtual(HomeHandler.class, "handle", fType);

Handler<RoutingContext> lambda = (Handler<RoutingContext>) LambdaMetafactory.metafactory(
    lookup, "handle", MethodType.methodType(Handler.class, HomeHandler.class),
    fType.erase(), mh, fType).getTarget().invokeExact(new HomeHandler());

Вы собираетесь вызвать метод экземпляра с связанным получателем, а тип целевого метода, исключая получателя, идентичен параметру instantiatedMethodType.Кроме того, поскольку предел T в Handler<T> равен Object, вы можете просто использовать erase() для этого типа метода, чтобы получить стертую подпись для параметра samMethodType.

Это не всегдатак просто.Попробуйте связать метод static int method(int x) с Consumer<Integer>.Тогда параметр samMethodType равен (Object)void, параметр instantiatedMethodType равен (Integer)void, а сигнатура целевого метода - int(int).Вам нужны все эти параметры, чтобы правильно описать код для генерации.Учитывая, что другие (первые три) параметры обычно заполняются JVM в любом случае, этот метод уже требует только необходимый минимум.

Во-вторых, если вам не нужна максимальная производительность, вы можете просто использоватьреализация на основе Proxy:

MethodHandle mh = MethodHandles.lookup().findVirtual(HomeHandler.class,
    "handle", MethodType.methodType(void.class, RoutingContext.class));
Handler<RoutingContext> lambda = MethodHandleProxies.asInterfaceInstance(
    Handler.class, mh.bindTo(new HomeHandler()));

Эта опция существует даже с Java 7

0 голосов
/ 14 октября 2018

Или я должен был создать отдельный экземпляр и как-то передать его в API?

Да.HomeHandler::handle - это метод экземпляра, который означает, что вам нужен экземпляр для создания оболочки функционального интерфейса или передачи экземпляра каждый раз, когда вы вызываете его (для которого Handler не будет работать как тип FunctionalInterface).

Чтобы использовать захваченный экземпляр, вы должны:

  • Изменить factoryMethodType, чтобы также взять HomeHandler экземпляр
  • Измените functionMethodType на стертый тип SAM,который принимает Object в качестве аргумента.
  • Измените аргумент instantiatedMethodType на тип дескриптора целевого метода без захваченного экземпляра HomeHandler (поскольку он захвачен, он вам больше не нужен какпараметр).
  • Передача экземпляра HomeHandler в invokeExact при создании интерфейса функционального интерфейса.

-

Class<?> homeHandlerClass = HomeHandler.class;

Method method = homeHandlerClass.getDeclaredMethod(
        "handle", RoutingContext.class);
Lookup lookup = MethodHandles.lookup();
MethodHandle mh = lookup.unreflect(method);

MethodType factoryMethodType = MethodType.methodType(Handler.class, HomeHandler.class);
MethodType functionMethodType = MethodType.methodType(void.class, Object.class);
MethodHandle implementationMethodHandle = mh;

Handler<RoutingContext> lambda =
        (Handler<RoutingContext>) LambdaMetafactory.metafactory(
                lookup,
                "handle",
                factoryMethodType, 
                functionMethodType,
                implementationMethodHandle,
                implementationMethodHandle.type().dropParameterTypes(0, 1)) 
        .getTarget()
        .invokeExact(new HomeHandler()); // capturing instance
lambda.handle(ctx);

OfКонечно, поскольку HomeHandler реализует Handler, вы можете просто использовать захваченный экземпляр напрямую;

new HomeHandler().handle(ctx);

Или использовать компилятор для генерации метафорического кода, который также использует invokedynamic, что означаетчто CallSite, возвращенный LambdaMetafactory.metafactory, будет включенможет быть создан один раз:

Handler<RoutingContext> lambda = new HomeHandler()::handle;
lambda.handle(ctx);

Или, если тип функционального интерфейса статически известен:

MethodHandle theHandle = ...
Object theInstance = ...
MethodHandle adapted = theHandle.bindTo(theInstance);
Handler<RoutingContext> lambda = ctxt -> {
    try {
        adapted.invokeExact(ctxt);
    } catch (Throwable e) {
        throw new RuntimeException(e);
    }
};
lambda.handle(new RoutingContext());
...