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: получить фактический тип универсального метода с параметром лямбда
Ответы
Ответ 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
в качестве суперинтерфейса, чтобы этот метод работал.