Как можно динамически преобразовать объект метода в функциональный интерфейс в Java?

У меня есть несколько классов, каждый из которых реализует интерфейс. Из этих классов я ищу метод, используя аннотацию. Этот метод возвращает логическое значение и всегда имеет объект в качестве параметра, который всегда наследуется от другого фиксированного объекта. Теперь я хочу создать функциональный интерфейс из этого метода. Оптимально конечно предикат, который берет на себя упомянутый параметр. Это я уже некоторое время пытался реализовать с помощью LambdaMetafactory:

private Predicate<Parent> toPredicate(final ExampleInterface instance, final Method method) {
    try {
        final MethodHandle handle = LOOKUP.unreflect(method);
        final MethodType signature = MethodType.methodType(boolean.class, method.getParameters()[0].getType());

        final CallSite callSite = LambdaMetafactory.metafactory(
            LOOKUP,
            "test",
            MethodType.methodType(Predicate.class, instance.getClass()),
            signature,
            handle,
            signature
        );

        return (Predicate<Parent>) callSite.getTarget().invoke(instance);
    } catch (Throwable e) {
        e.printStackTrace();
        return null;
    }
}

Теперь моя проблема заключается в том, что когда я вызываю тестовый метод Predicate, выдается AbstractMethodError. Когда я использую подпись с (boolean.class, Parent.class), я получаю исключение LambdaConversionException. Возможно ли вообще реализовать это динамически? Если да, то как?

Четвертый параметр metafactory должен быть signature.erase(). Тем не менее, вы понимаете, что Predicate<Cat> не Predicate<Animal>? Последний может проверить собаку, а первый нет. Наоборот, Predicate<Animal> есть Predicate<? super Cat>.

Sweeper 15.02.2023 01:43

Я пробовал это раньше с «Object.class», а не «erase ()», чтобы выполнить стирание типа. Так это имеет значение? В любом случае, теперь это наконец работает, большое спасибо! И да, я хочу понять разницу между «Predicate<Cat>» и «Predicate<Animal>» — в конце концов, это простое наследование. Или вы все еще видите ошибку где-то в моем подходе?

zuzuri 15.02.2023 01:51

Я хочу сказать, что «этот метод возвращает логическое значение и всегда имеет объект в качестве параметра, который всегда наследуется от другого фиксированного объекта». это не Predicate<Parent>. Приводить его к этому небезопасно.

Sweeper 15.02.2023 01:54

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

zuzuri 15.02.2023 02:07
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
4
56
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Поскольку вы знаете, что тип целевого интерфейса — Predicate, вы также можете использовать лямбда-выражение:

private Predicate<Parent> toPredicate(final ExampleInterface instance, final Method method) {
    final MethodHandle handle = LOOKUP.unreflect(method)
        .bindTo(instance)
        .asType(MethodType.methodType(boolean.class, Parent.class);
    
    return parent -> {
        try {
            return (boolean) handle.invokeExact(parent);
        } catch (Throwable t) {
            throw new RuntimeException("Should not happen", t);
        }
    };
}

Использование LambdaMetafactory действительно полезно только в определенных случаях, когда конкретный сайт вызова видит только один экземпляр экземпляра интерфейса. Также обратите внимание, что каждый раз, когда вы создаете метафабрику, генерируется новый класс (если только его нельзя загрузить из архива CDS), с которым связаны собственные затраты.

То, что вы ошибаетесь, - это тип метода интерфейса. Это должно быть стирание метода test в Predicate:

final MethodHandle handle = LOOKUP.unreflect(method);

final CallSite callSite = LambdaMetafactory.metafactory(
    LOOKUP,
    "test",
    MethodType.methodType(Predicate.class, instance.getClass()),
    MethodType.methodType(boolean.class, Object.class),
    handle,
    MethodType.methodType(boolean.class, Parent.class)
);

Причина, по которой я выбрал свое текущее решение, заключается в том, что моя программа должна быть очень производительной. Мне кажется, что ваше первое решение создает Predicate, который каждый раз вызывает MethodHandle. К сожалению, в тесте MethodHandles намного медленнее, чем функциональные интерфейсы, сгенерированные LambdaMetafactory. Или я ошибаюсь здесь? Тем не менее спасибо за этот элегантный подход, который тоже сработал.

zuzuri 15.02.2023 02:01

@zuzuri Я не могу сказать, не видя точного эталона, который вы используете. После JIT-компиляции единственные накладные расходы, которые вы получаете от вызова дескриптора метода, — это переход через небольшую заглушку сборки, которая, как показали мои собственные тесты, не намного медленнее, чем обычный вызов метода Java. Основное преимущество LMF заключается во встраивании через интерфейсный метод (что на практике может никогда не произойти). Кроме того, как я уже сказал, LMF генерирует новый класс каждый раз, когда вы его создаете, так что вы можете выстрелить себе в ногу с этим с точки зрения производительности.

Jorn Vernee 15.02.2023 02:18

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

zuzuri 15.02.2023 02:21

Сейчас я разработал небольшой тест в своей программе, а не сырой, поэтому ему не следует доверять на 100%. В любом случае, я получил разницу во времени выполнения в 10%, тогда как метод с LMF кажется немного быстрее. Тем не менее, любой, кто читает это, должен снова разработать свой собственный тест и не полагаться на мое значение.

zuzuri 15.02.2023 02:49

Другие вопросы по теме