Как получить MethodInfo ссылки на метод Java 8?
пожалуйста, взгляните на следующий код:
Method methodInfo = MyClass.class.getMethod("myMethod");
это работает, но имя метода передается как строка, так что это будет компилироваться, даже если myMethod не существует.
С другой стороны, Java 8 вводит функцию ссылки на метод. Это проверяется во время компиляции. Можно ли использовать эту функцию для получения информации о методе?
printMethodName(MyClass::myMethod);
полный пример:
@FunctionalInterface
private interface Action {
void invoke();
}
private static class MyClass {
public static void myMethod() {
}
}
private static void printMethodName(Action action) {
}
public static void main(String[] args) throws NoSuchMethodException {
// This works, but method name is passed as a string, so this will compile
// even if myMethod does not exist
Method methodInfo = MyClass.class.getMethod("myMethod");
// Here we pass reference to a method. It is somehow possible to
// obtain java.lang.reflect.Method for myMethod inside printMethodName?
printMethodName(MyClass::myMethod);
}
другими словами, Я хотел бы иметь код, который является эквивалентом следующий код C#:
private static class InnerClass
{
public static void MyMethod()
{
Console.WriteLine("Hello");
}
}
static void PrintMethodName(Action action)
{
// Can I get java.lang.reflect.Method in the same way?
MethodInfo methodInfo = action.GetMethodInfo();
}
static void Main()
{
PrintMethodName(InnerClass.MyMethod);
}
10 ответов:
нет, нет надежного, поддерживаемого способа сделать это. Вы назначаете ссылку на метод экземпляру функционального интерфейса, но этот экземпляр готовится с помощью
LambdaMetaFactory, и нет никакого способа просверлить его, чтобы найти метод, к которому вы изначально привязаны.ссылки на лямбды и методы в Java работают совершенно иначе, чем делегаты в C#. Для некоторого интересного фона, читайте на
invokedynamic.другие ответы и комментарии здесь показывают, что в настоящее время она может быть можно получить связанный метод с некоторой дополнительной работой, но убедитесь, что вы понимаете предостережения.
похоже, что это может быть возможно, в конце концов, с помощью прокси-сервера для записи, Какой метод вызывается.
в моем случае я ищу способ избавиться от этого в модульных тестах:
Point p = getAPoint(); assertEquals(p.getX(), 4, "x"); assertEquals(p.getY(), 6, "x");как вы можете видеть, кто-то тестирует метод
getAPointи проверяет, что координаты соответствуют ожидаемым, но в описании каждого утверждения были скопированы и не синхронизированы с тем, что проверено. Лучше было бы написать это только один раз.из идей @ddan я построил прокси-решение с помощью Mockito:
private<T> void assertPropertyEqual(final T object, final Function<T, ?> getter, final Object expected) { final String methodName = getMethodName(object.getClass(), getter); assertEquals(getter.apply(object), expected, methodName); } @SuppressWarnings("unchecked") private<T> String getMethodName(final Class<?> clazz, final Function<T, ?> getter) { final Method[] method = new Method[1]; getter.apply((T)Mockito.mock(clazz, Mockito.withSettings().invocationListeners(methodInvocationReport -> { method[0] = ((InvocationOnMock) methodInvocationReport.getInvocation()).getMethod(); }))); return method[0].getName(); }Нет, я могу просто использовать
assertPropertyEqual(p, Point::getX, 4); assertPropertyEqual(p, Point::getY, 6);и описание утверждать гарантировано, чтобы быть в синхронизации с кодом.
недостаток:
- будет немного медленнее, чем выше
- нужно Mockito работать
- вряд ли полезно для чего-либо, кроме usecase выше.
однако он показывает способ, как это можно сделать.
хотя я сам не пробовал, я думаю, что ответ "нет", так как ссылка на метод семантически совпадает с лямбда.
Если вы можете сделать интерфейс
ActionрасширенияSerializable, потом ответ из другого вопроса, кажется, обеспечивает решение (по крайней мере, на некоторых компиляторах и времени выполнения).
мы опубликовали небольшую библиотеку de.cronn: reflection-util это может быть использовано для захвата имени метода.
пример:
class MyClass { public void myMethod() { } } String methodName = ClassUtils.getVoidMethodName(MyClass.class, MyClass::myMethod); System.out.println(methodName); // prints "myMethod"детали реализации: прокси-подкласс
MyClassсоздано ByteBuddy и вызов метода для получения его имени.ClassUtilsкэширует информацию таким образом, что нам не нужно создавать новый прокси при каждом вызове.обратите внимание, что такой подход не работает с статический методы.
там не может быть надежным, но при некоторых обстоятельствах:
- код
MyClassне является окончательным, и имеет доступный конструктор (ограничение cglib)- код
myMethodне перегружен, и не статиченвы можете попробовать использовать cglib для создания прокси-сервера
MyClass, затем с помощьюMethodInterceptorв отчетеMethodв то время как ссылка на метод вызывается в следующем пробном запуске.пример код:
public static void main(String[] args) { Method m = MethodReferenceUtils.getReferencedMethod(ArrayList.class, ArrayList::contains); System.out.println(m); }вы увидите следующий результат:
public boolean java.util.ArrayList.contains(java.lang.Object)время:
public class MethodReferenceUtils { @FunctionalInterface public static interface MethodRefWith1Arg<T, A1> { void call(T t, A1 a1); } public static <T, A1> Method getReferencedMethod(Class<T> clazz, MethodRefWith1Arg<T, A1> methodRef) { return findReferencedMethod(clazz, t -> methodRef.call(t, null)); } @SuppressWarnings("unchecked") private static <T> Method findReferencedMethod(Class<T> clazz, Consumer<T> invoker) { AtomicReference<Method> ref = new AtomicReference<>(); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(clazz); enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { ref.set(method); return null; } }); try { invoker.accept((T) enhancer.create()); } catch (ClassCastException e) { throw new IllegalArgumentException(String.format("Invalid method reference on class [%s]", clazz)); } Method method = ref.get(); if (method == null) { throw new IllegalArgumentException(String.format("Invalid method reference on class [%s]", clazz)); } return method; } }в приведенном выше коде
MethodRefWith1Argэто просто синтаксический сахар для вас, чтобы ссылаться на нестатический метод с одним аргументом. Вы можете создать столько, сколькоMethodRefWithXArgsдля ссылки на другие методы.
Итак, я играю с этим кодом
import sun.reflect.ConstantPool; import java.lang.reflect.Method; import java.util.function.Consumer; public class Main { private Consumer<String> consumer; Main() { consumer = this::test; } public void test(String val) { System.out.println("val = " + val); } public void run() throws Exception { ConstantPool oa = sun.misc.SharedSecrets.getJavaLangAccess().getConstantPool(consumer.getClass()); for (int i = 0; i < oa.getSize(); i++) { try { Object v = oa.getMethodAt(i); if (v instanceof Method) { System.out.println("index = " + i + ", method = " + v); } } catch (Exception e) { } } } public static void main(String[] args) throws Exception { new Main().run(); } }вывод этого кода:
index = 30, method = public void Main.test(java.lang.String)и как я заметил индекс ссылочного метода всегда 30. Окончательный код может выглядеть как
public Method unreference(Object methodRef) { ConstantPool constantPool = sun.misc.SharedSecrets.getJavaLangAccess().getConstantPool(methodRef.getClass()); try { Object method = constantPool.getMethodAt(30); if (method instanceof Method) { return (Method) method; } }catch (Exception ignored) { } throw new IllegalArgumentException("Not a method reference."); }будьте осторожны с этим кодом в производстве!
Вы можете использовать мою библиотеку
Reflect Without StringMethod myMethod = ReflectWithoutString.methodGetter(MyClass.class).getMethod(MyClass::myMethod);
Comments