it-swarm.com.ru

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

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

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

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

22
Mike Strobel

Похоже, что это все-таки возможно, используя прокси для записи, какой метод вызывается.

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

14
ddan

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

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);

и описание assert гарантированно синхронизируется с кодом.

Даунсайд:

  • Будет немного медленнее чем выше
  • Требуется Мокито для работы
  • Вряд ли полезно для чего-либо, кроме описанного выше.

Однако это показывает, как это можно сделать.

7
yankee

Хотя я сам не пробовал, я думаю, что ответ «нет», поскольку ссылка на метод семантически совпадает с лямбда-выражением.

5
Matt Ball

Если вы можете сделать интерфейс Action расширенным Serializable, то этот ответ из другого вопроса, кажется, обеспечивает решение (по крайней мере, для некоторых компиляторов и сред выполнения).

3
user102008

Мы опубликовали небольшую библиотеку de.cronn: отражение-util , которую можно использовать для захвата имени метода.

Пример:

class MyClass {

    public void myMethod() {
    }

}

String methodName = ClassUtils.getVoidMethodName(MyClass.class, MyClass::myMethod);
System.out.println(methodName); // prints "myMethod"

Детали реализации: Подкласс прокси MyClass создается с помощью ByteBuddy и захватывается метод для получения его имени .ClassUtils кэширует информацию таким образом, что нам не нужно создавать новый прокси при каждом вызове ,.

Обратите внимание, что этот подход не работает с методами static.

2
Benedikt Waldvogel

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

  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 для ссылки на другие ваши методы.

1
vivimice

Вы можете добавить safety-mirror к вашему classpath и сделать так:

Method m1 = Types.createMethod(Thread::isAlive)  // Get final method
Method m2 = Types.createMethod(String::isEmpty); // Get method from final class
Method m3 = Types.createMethod(BufferedReader::readLine); // Get method that throws checked exception
Method m4 = Types.<String, Class[]>createMethod(getClass()::getDeclaredMethod); //to get vararg method you must specify parameters in generics
Method m5 = Types.<String>createMethod(Class::forName); // to get overloaded method you must specify parameters in generics
Method m6 = Types.createMethod(this::toString); //Works with inherited methods

Библиотека также предлагает метод getName(...):

assertEquals("isEmpty", Types.getName(String::isEmpty));

Библиотека основана на ответе Хольгера: https://stackoverflow.com/a/21879031/6095334

Правка: Библиотека имеет различные недостатки, о которых я постепенно узнаю . См. Комментарий Ф. Хольгера здесь: Как получить имя метода, получающегося в результате лямбды

1
Hervian

Вы можете использовать мою библиотеку Reflect Without String

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