Вывод типа отражения на Java 8 Lambdas
Я экспериментировал с новым Lambdas в Java 8, и я ищу способ использовать reflection на lambda-classах, чтобы получить возвращаемый тип lambda-функции. Меня особенно интересуют случаи, когда lambda реализует общий суперинтерфейс. В приведенном ниже примере кода MapFunction
является общим суперинтерфейсом, и я ищу способ узнать, какой тип привязан к общему параметру T
В то время как Java сбрасывает много информации о родовом типе после компилятора, подclassы (и анонимные подclassы) общих суперclassов и общих суперинтерфейсов сохраняли эту информацию о типе. Благодаря отражению эти типы были доступны. В приведенном ниже примере (случай 1) reflection говорит мне, что реализация MapFunction
в MapFunction
связывает java.lang.Integer
с параметром типового типа T
Даже для подclassов, которые сами по себе являются универсальными, существуют определенные способы выяснить, что связывается с общим параметром, если известны некоторые другие. Рассмотрим пример 2 в приведенном ниже примере: IdentityMapper
где оба F
и T
привязаны к одному типу. Когда мы это знаем, мы знаем тип F
если мы знаем тип параметра T
(что в моем случае мы делаем).
- Как вызвать метод расширения с помощью отражения?
- Установка свойства путем отражения со строковым значением
- Как я могу получить примитивное имя типа в C #?
- Получение значения открытого статического конечного поля / свойства classа в Java через reflection
- Как использовать reflection для вызова общего метода?
Вопрос в том, как я могу реализовать что-то подобное для Java 8 lambdas? Поскольку они на самом деле не являются регулярными подclassами общего суперинтерфейса, описанный выше метод не работает. В частности, могу ли я понять, что parseLambda
связывает java.lang.Integer
с T
, а identityLambda
связывает то же с F
и T
?
PS: Теоретически можно декомпилировать lambda-код, а затем использовать встроенный компилятор (например, JDT) и коснуться его вывода типа. Я надеюсь, что есть более простой способ сделать это 😉
/** * The superinterface. */ public interface MapFunction { T map(F value); } /** * Case 1: A non-generic subclass. */ public class MyMapper implements MapFunction { public Integer map(String value) { return Integer.valueOf(value); } } /** * A generic subclass */ public class IdentityMapper implements MapFunction { public E map(E value) { return value; } } /** * Instantiation through lambda */ public MapFunction parseLambda = (String str) -> { return Integer.valueOf(str); } public MapFunction identityLambda = (value) -> { return value; } public static void main(String[] args) { // case 1 getReturnType(MyMapper.class); // -> returns java.lang.Integer // case 2 getReturnTypeRelativeToParameter(IdentityMapper.class, String.class); // -> returns java.lang.String } private static Class getReturnType(Class implementingClass) { Type superType = implementingClass.getGenericInterfaces()[0]; if (superType instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) superType; return (Class) parameterizedType.getActualTypeArguments()[1]; } else return null; } private static Class getReturnTypeRelativeToParameter(Class implementingClass, Class parameterType) { Type superType = implementingClass.getGenericInterfaces()[0]; if (superType instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) superType; TypeVariable inputType = (TypeVariable) parameterizedType.getActualTypeArguments()[0]; TypeVariable returnType = (TypeVariable) parameterizedType.getActualTypeArguments()[1]; if (inputType.getName().equals(returnType.getName())) { return parameterType; } else { // some logic that figures out composed return types } } return null; }
- могу ли я отразить типичный тип в java?
- Как получить class Array для данного classа в Java?
- Создание собственного внутреннего classа с reflectionм java
- Проверьте, является ли class производным от общего classа
- Обнаружение производных типов с использованием отражения
- Поиск всех пространств имен в сборке с использованием Reflection (DotNET)
- GetProperties () возвращает все свойства иерархии наследования интерфейса
- Использование Case / Switch и GetType для определения объекта
Точное решение о том, как сопоставить lambda-код для реализации интерфейса, остается в реальной среде выполнения. В принципе, все lambdas, реализующие один и тот же исходный интерфейс, могут совместно использовать один class выполнения, как MethodHandleProxies
делает MethodHandleProxies
. Использование разных classов для конкретных lambdas – это оптимизация, выполняемая фактической реализацией LambdaMetafactory
но не функция, предназначенная для помощи в отладке или отражении.
Таким образом, даже если вы найдете более подробную информацию в реальном classе выполнения для реализации lambda-интерфейса, это будет артефактом используемой среды выполнения, которая может быть недоступна в разной реализации или даже в других версиях вашей текущей среды.
Если lambda является Serializable
вы можете использовать тот факт, что сериализованная форма содержит сигнатуру метода созданного типа интерфейса для одновременного изменения значений переменных фактического типа.
В настоящее время это можно решить, но только в довольно хаки, но позвольте мне сначала объяснить несколько вещей:
Когда вы пишете lambda, компилятор вводит инструкцию динамического вызова, указывающую на LambdaMetafactory и частный статический синтетический метод с телом lambda. Синтетический метод и дескриптор метода в постоянном пуле содержат общий тип (если lambda использует этот тип или явственна, как в ваших примерах).
Теперь во время выполнения LambdaMetaFactory
и генерируется class с использованием ASM, который реализует функциональный интерфейс, а тело метода затем вызывает частный статический метод с любыми переданными аргументами. Затем он вводится в исходный class, используя Unsafe.defineAnonymousClass
(см. Unsafe.defineAnonymousClass
Джона Роуза ), чтобы он мог получить доступ к закрытым членам и т. Д.
К сожалению, сгенерированный class не хранит общие подписи (он может), поэтому вы не можете использовать обычные методы отражения, которые позволят вам обойти стирание
Для обычного classа вы можете проверить байт-код с помощью Class.getResource(ClassName + ".class")
но для анонимных classов, определенных с помощью Unsafe
вам не повезло. Однако вы можете заставить LambdaMetaFactory
сбрасывать их с помощью аргумента JVM:
java -Djdk.internal.lambda.dumpProxyClasses=/some/folder
javap -p -s -v
файл сбрасываемого classа (используя javap -p -s -v
), можно увидеть, что он действительно вызывает статический метод. Но проблема остается в том, как получить байт-код из самой Java.
К сожалению, это хакки:
Используя reflection, мы можем вызвать 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);
Обновлено с предложением jonathan
Теперь, в идеале, classы, созданные LambdaMetaFactory
должны хранить сигнатуры общего типа (я могу увидеть, могу ли я предоставить патч для OpenJDK), но в настоящее время это лучшее, что мы можем сделать. В приведенном выше коде есть следующие проблемы:
- Он использует недокументированные методы и classы
- Он чрезвычайно уязвим для изменения кода в JDK
- Он не сохраняет общие типы, поэтому, если вы передадите List
в lambda, он выйдет как List
Информация с параметризованным типом доступна только во время выполнения для элементов кода, которые связаны, то есть специально скомпилированы в тип. Лямбдас делает то же самое, но поскольку ваша Лямбда не привязана к методу, а не к типу, нет никакого типа для захвата этой информации.
Рассмотрим следующее:
import java.util.Arrays; import java.util.function.Function; public class Erasure { static class RetainedFunction implements Function { public String apply(Integer t) { return String.valueOf(t); } } public static void main(String[] args) throws Exception { Function f0 = new RetainedFunction(); Function f1 = new Function() { public String apply(Integer t) { return String.valueOf(t); } }; Function f2 = String::valueOf; Function f3 = i -> String.valueOf(i); for (Function f : Arrays.asList(f0, f1, f2, f3)) { try { System.out.println(f.getClass().getMethod("apply", Integer.class).toString()); } catch (NoSuchMethodException e) { System.out.println(f.getClass().getMethod("apply", Object.class).toString()); } System.out.println(Arrays.toString(f.getClass().getGenericInterfaces())); } } }
f0
и f1
сохраняют информацию об общем типе, как и следовало ожидать. Но поскольку они являются несвязанными методами, которые были стерты для Function
, f2
и f3
, нет.
Недавно я добавил поддержку для разрешения аргументов типа lambda для TypeTools . Пример:
MapFunction fn = str -> Integer.valueOf(str); Class[] typeArgs = TypeResolver.resolveRawArguments(MapFunction.class, fn.getClass());
Разрешенные аргументы типа соответствуют ожиданиям:
assert typeArgs[0] == String.class; assert typeArgs[1] == Integer.class;
Чтобы обработать пройденную лямбду:
public void call(Callable c) { // Assumes c is a lambda Class callableType = TypeResolver.resolveRawArguments(Callable.class, c.getClass()); }
Примечание. В базовой реализации используется подход ConstantPool, описанный @danielbodart, который, как известно, работает с Oracle JDK и OpenJDK (и, возможно, с другими).
Я нашел способ сделать это для сериализуемых lambda. Все мои лямбды являются сериализуемыми, к этим работам.
Спасибо, Хольгер, за то, что указал мне на SerializedLambda
.
Общие параметры фиксируются в синтетическом статическом методе lambda и могут быть получены оттуда. Поиск статического метода, реализующего lambda, возможно с помощью информации из SerializedLambda
Эти шаги заключаются в следующем:
- Получите SerializedLambda с помощью метода замещения записи, который автоматически генерируется для всех сериализуемых lambda
- Найдите class, содержащий реализацию lambda (как синтетический статический метод)
- Получить
java.lang.reflect.Method
для синтетического статического метода - Получить общие типы из этого
Method
ОБНОВЛЕНИЕ: По-видимому, это не работает со всеми компиляторами. Я пробовал его с помощью компилятора Eclipse Luna (works) и Oracle javac (не работает).
// sample how to use public static interface SomeFunction extends java.io.Serializable { List applyTheFunction(Set value); } public static void main(String[] args) throws Exception { SomeFunction lambda = (set) -> Collections.singletonList(set.iterator().next().longValue()); SerializedLambda sl = getSerializedLambda(lambda); Method m = getLambdaMethod(sl); System.out.println(m); System.out.println(m.getGenericReturnType()); for (Type t : m.getGenericParameterTypes()) { System.out.println(t); } // prints the following // (the method) private static java.util.List test.ClassWithLambdas.lambda$0(java.util.Set) // (the return type, including *Long* as the generic list type) java.util.List // (the parameter, including *Double* as the generic set type) java.util.Set
// getting the SerializedLambda public static SerializedLambda getSerializedLambda(Object function) { if (function == null || !(function instanceof java.io.Serializable)) { throw new IllegalArgumentException(); } for (Class clazz = function.getClass(); clazz != null; clazz = clazz.getSuperclass()) { try { Method replaceMethod = clazz.getDeclaredMethod("writeReplace"); replaceMethod.setAccessible(true); Object serializedForm = replaceMethod.invoke(function); if (serializedForm instanceof SerializedLambda) { return (SerializedLambda) serializedForm; } } catch (NoSuchMethodError e) { // fall through the loop and try the next class } catch (Throwable t) { throw new RuntimeException("Error while extracting serialized lambda", t); } } throw new Exception("writeReplace method not found"); }
// getting the synthetic static lambda method public static Method getLambdaMethod(SerializedLambda lambda) throws Exception { String implClassName = lambda.getImplClass().replace('/', '.'); Class implClass = Class.forName(implClassName); String lambdaName = lambda.getImplMethodName(); for (Method m : implClass.getDeclaredMethods()) { if (m.getName().equals(lambdaName)) { return m; } } throw new Exception("Lambda Method not found"); }