Как получить 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);
}
Другими словами, я хотел бы иметь код, который является эквивалентом следующего кода С#:
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);
}
Ответы
Ответ 1
Нет, нет надежного, поддерживаемого способа сделать это. Вы назначаете ссылку метода на экземпляр функционального интерфейса, но этот экземпляр создается LambdaMetaFactory
, и нет способа просверлить его, чтобы найти метод, с которым вы первоначально привязывались.
Lambdas и ссылки на методы в Java работают совсем иначе, чем делегаты на С#. Для некоторого интересного фона прочитайте invokedynamic
.
Другие ответы и комментарии здесь показывают, что в настоящее время возможно получить связанный метод с некоторой дополнительной работой, но убедитесь, что вы понимаете оговорки.
Ответ 2
Похоже, что это возможно в конце концов, используя прокси для записи того, какой метод вызывается.
fooobar.com/questions/117356/...
Ответ 3
Хотя я сам не пробовал, я думаю, что ответ "нет", поскольку ссылка на метод семантически такая же, как и лямбда.
Ответ 4
В моем случае я искал способ избавиться от этого в модульных тестах:
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 выше.
Однако он показывает способ, как это можно сделать.
Ответ 5
Если вы можете сделать интерфейс Action
extend Serializable
, то этот ответ из другого вопроса, похоже, предоставит решение (по крайней мере, на некоторых компиляторах и во время выполнения).
Ответ 6
Мы опубликовали небольшую библиотеку 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
кэширует информацию таким образом, что нам не нужно создавать новый прокси-сервер при каждом вызове.
Обратите внимание, что этот подход не работает с методами static.
Ответ 7
Не может быть надежного способа, но при некоторых обстоятельствах:
- ваш
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
для ссылок на другие методы.
Ответ 8
Попробуйте это
Thread.currentThread().getStackTrace()[2].getMethodName();
Ответ 9
Итак, я играю с этим кодом
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.");
}
Будьте осторожны с этим кодом в производстве!
Ответ 10
Вы можете использовать мою библиотеку Reflect Without String
Method myMethod = ReflectWithoutString.methodGetter(MyClass.class).getMethod(MyClass::myMethod);