Как получить 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);
}
689   10  

10 ответов:

нет, нет надежного, поддерживаемого способа сделать это. Вы назначаете ссылку на метод экземпляру функционального интерфейса, но этот экземпляр готовится с помощью LambdaMetaFactory, и нет никакого способа просверлить его, чтобы найти метод, к которому вы изначально привязаны.

ссылки на лямбды и методы в Java работают совершенно иначе, чем делегаты в C#. Для некоторого интересного фона, читайте на invokedynamic.

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

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

https://stackoverflow.com/a/22745127/3478229

в моем случае я ищу способ избавиться от этого в модульных тестах:

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 кэширует информацию таким образом, что нам не нужно создавать новый прокси при каждом вызове.

обратите внимание, что такой подход не работает с статический методы.

попробуй такое

Thread.currentThread().getStackTrace()[2].getMethodName();

там не может быть надежным, но при некоторых обстоятельствах:

  1. код MyClass не является окончательным, и имеет доступный конструктор (ограничение cglib)
  2. код 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 String

Method myMethod = ReflectWithoutString.methodGetter(MyClass.class).getMethod(MyClass::myMethod);

Comments

    Ничего не найдено.