Подтвердить что ты не робот

Java: как разрешить общий тип параметра лямбда?

Ну, у нас есть FunctionalInterface:

public interface Consumer<T> {

    void accept(T t);

}

И я могу использовать его как:

.handle(Integer p -> System.out.println(p * 2));

Как мы можем разрешить фактический generic type этого лямбда-параметра в нашем коде?

Когда мы используем его как встроенную реализацию, не так сложно извлечь Integer из метода этого класса.

Пропустить что-нибудь? Или просто java не поддерживает его для лямбда-классов?

Чтобы быть более чистым:

Эта лямбда завернута в MethodInvoker (в упомянутом handle), который в своем execute(Message<?> message) извлекает фактические параметры для дальнейшего вызова метода отражения. До этого он преобразует предоставленные аргументы в целевые параметры с помощью Spring ConversionService.

Метод handle в этом случае является некоторой конфигурацией до того, как реальная работа приложения.

Другой вопрос, но с ожиданием решения для той же проблемы: Java: получить фактический тип универсального метода с параметром лямбда

4b9b3361

Ответ 1

Недавно я добавил поддержку для разрешения аргументов лямбда-типа для TypeTools. Пример:

MapFunction<String, Integer> fn = str -> Integer.valueOf(str);
Class<?>[] typeArgs = TypeResolver.resolveRawArguments(MapFunction.class, fn.getClass());

Аргументы разрешенных типов как ожидалось:

assert typeArgs[0] == String.class;
assert typeArgs[1] == Integer.class;

Примечание. В базовой реализации используется подход ConstantPool, описанный @danielbodart, который, как известно, работает с Oracle JDK и OpenJDK.

Ответ 2

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

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

Теперь во время выполнения вызывается LambdaMetaFactory и генерируется класс с использованием ASM, который реализует функциональный интерфейс, а тело метода затем вызывает частный статический метод с любыми переданными аргументами. Затем он вводится в исходный класс, используя Unsafe.defineAnonymousClass (см. John Rose post), чтобы он мог получить доступ к закрытым членам и т.д.

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

Для обычного класса вы можете проверить байт-код с помощью Class.getResource(ClassName + ".class"), но для анонимных классов, определенных с помощью Unsafe, вам не повезло. Однако вы можете сделать LambdaMetaFactory сброс их с помощью аргумента JVM:

java -Djdk.internal.lambda.dumpProxyClasses=/some/folder

Изучив файл сбрасываемого класса (используя javap -p -s -v), можно увидеть, что он действительно вызывает статический метод. Но проблема остается в том, как получить байт-код из самой Java.

К сожалению, это хакки:

Используя отражение, мы можем вызвать Class.getConstantPool, а затем получить доступ к MethodRefInfo, чтобы получить дескрипторы типа. Затем мы можем использовать ASM для синтаксического анализа этого и возвращения типов аргументов. Объединяя все это:

Method getConstantPool = Class.class.getDeclaredMethod("getConstantPool");
getConstantPool.setAccessible(true);
ConstantPool constantPool = (ConstantPool) getConstantPool.invoke(lambda.getClass());
String[] methodRefInfo = constantPool.getMemberRefInfoAt(constantPool.size() - 2);

int argumentIndex = 0;
String argumentType = jdk.internal.org.objectweb.asm.Type.getArgumentTypes(methodRef[2])[argumentIndex].getClassName();
Class<?> type = (Class<?>) Class.forName(argumentType);

ОБНОВЛЕНО с предложением Джонатана

Теперь в идеале классы, сгенерированные LambdaMetaFactory, должны хранить сигнатуры общего типа (я мог бы увидеть, могу ли я предоставить патч OpenJDK), но в настоящее время это лучшее, что мы можем сделать. В приведенном выше коде есть следующие проблемы:

  • Он использует недокументированные методы и классы
  • Он чрезвычайно уязвим для изменений кода в JDK
  • Он не сохраняет общие типы, поэтому, если вы передадите List <String> в лямбда он выйдет как List

Ответ 3

Если ваши Lambdas сериализуемы (интерфейсы SAM простираются от java.io.Serializable), тогда это решение может сделать это для вас:

Вывод типа рефлекса на Java 8 Lambdas

Если вы сами создали интерфейсы SAM, возможно, стоит добавить java.io.Serializable в качестве суперинтерфейса, чтобы этот метод работал.