Как заставить новую реализацию лямда-определения - PullRequest
5 голосов
/ 30 апреля 2019

Java-Spec гарантирует, что данное лямда-определение, например, () -> "Hello World", компилируется / преобразуется ровно в один класс реализации (каждое определение, а не каждый случай, который «выглядит» одинаково).

Есть ли способ заставить java-compiler / jvm сгенерировать новое lamda-определение вместо общего? В настоящее время я реализую библиотеку, которая сплетает несколько функциональных частей в BiFunction, которая страдает от мегаморфных call-сайтов из-за гарантий, предоставленных java-spec ( РЕДАКТИРОВАТЬ: Я исправлен: Java-Spec не гарантирует один общий класс - текущая эталонная реализация делает это, хотя ):

        public <In, Out, A> BiFunction<In, Out, Out> weave(
             Function<? super In, A> getter,
             BiConsumer<? super Out, ? super A> consumer
        ) {
            return (in, out) -> {
                consumer.accept(out, getter.apply(in));
                return out;
            };
        }

Каждая лямда, сгенерированная с помощью этого кода, имеет одно и то же определение лямды и, таким образом, в большинстве случаев является неопределяемой / неоптимизируемой.

1 Ответ

2 голосов
/ 30 апреля 2019

В текущей реализации кэширование сгенерированных классов (или даже экземпляров для не захватывающих лямбда-выражений) является свойством инструкции invokedynamic, которая будет повторно использовать результат начальной загрузки, выполненной в первое исполнение.

Сам метод начальной загрузки, размещенный в классе LambdaMetafactory, будет генерировать новый класс каждый раз, когда он вызывается. Поэтому, когда вы используете эту фабрику напрямую, вы будете получать новый класс при каждом вызове в соответствии с текущей реализацией.

public <In, Out, A> BiFunction<In, Out, Out> weave(
     Function<? super In, A> getter,
     BiConsumer<? super Out, ? super A> consumer) {

    MethodHandles.Lookup l = MethodHandles.lookup();
    try {
        MethodHandle target = l.findStatic(l.lookupClass(), "weaveLambdaBody",
            MethodType.methodType(Object.class, Function.class, BiConsumer.class,
                Object.class, Object.class));
        MethodType t = target.type().dropParameterTypes(0, 2);
        return (BiFunction<In, Out, Out>)LambdaMetafactory.metafactory(l, "apply",
            target.type().dropParameterTypes(2, 4).changeReturnType(BiFunction.class),
            t, target, t) .getTarget().invokeExact(getter, consumer);
    }
    catch(RuntimeException | Error e) {
        throw e;
    }
    catch(Throwable t) {
        throw new IllegalStateException(t);
    }
}
private static <In, Out, A> Out weaveLambdaBody(
    Function<? super In, A> getter,
    BiConsumer<? super Out, ? super A> consumer,
    In in, Out out) {

    consumer.accept(out, getter.apply(in));
    return out;
}

Во-первых, вы должны десугарировать лямбда-тело в метод. Захваченные значения идут первыми в списке параметров, за которыми следуют параметры типа функционального интерфейса. LambdaMetafactory содержит исчерпывающую документацию по его использованию.

Хотя я сохранил параметры типа для документирования, должно быть ясно, что при такой операции вы теряете безопасность во время компиляции.

...