Comment obtenir le MethodInfo d'une référence de méthode Java 8?


Veuillez consulter le code suivant:

Method methodInfo = MyClass.class.getMethod("myMethod");

Cela fonctionne, mais le nom de la méthode est passé sous forme de chaîne, donc cela compilera même si myMethod n'existe pas.

D'autre part, Java 8 introduit une fonction de référence de méthode. Il est vérifié au moment de la compilation. Il est possible d'utiliser cette fonctionnalité pour obtenir des informations sur la méthode?

printMethodName(MyClass::myMethod);

Exemple Complet:

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

En d'autres termes, je voudrais avoir un code qui est l'équivalent du code C# suivant:

    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);
    }
Author: Rafal, 2013-11-07

10 answers

Non, il n'y a pas de moyen fiable et pris en charge de le faire. Vous attribuez une référence de méthode à une instance d'une interface fonctionnelle, mais cette instance est préparée par LambdaMetaFactory, et il n'y a aucun moyen de l'explorer pour trouver la méthode à laquelle vous êtes initialement lié.

Les lambdas et les références de méthodes en Java fonctionnent très différemment des délégués en C#. Pour un contexte intéressant, lisez invokedynamic.

Autres réponses et commentaires ici montrent qu'il peut être possible de récupérer le méthode liée avec un travail supplémentaire, mais assurez-vous de comprendre les mises en garde.

 20
Author: Mike Strobel, 2016-01-20 17:50:29

On dirait que cela pourrait être possible après tout, en utilisant un proxy pour enregistrer quelle méthode est appelée.

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

 12
Author: ddan, 2017-05-23 12:10:34

Dans mon cas, je cherchais un moyen de se débarrasser de ce dans les tests unitaires:

Point p = getAPoint();
assertEquals(p.getX(), 4, "x");
assertEquals(p.getY(), 6, "x");

Comme vous pouvez le voir, quelqu'un teste la méthode getAPoint et vérifie que les coordonnées sont comme prévu, mais dans la description de chaque affirmation a été copiée et n'est pas synchronisée avec ce qui est vérifié. Mieux serait d'écrire cela une seule fois.

À partir des idées de @ddan, j'ai construit une solution proxy en utilisant 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();
}

Non, je peux simplement utiliser

assertPropertyEqual(p, Point::getX, 4);
assertPropertyEqual(p, Point::getY, 6);

Et la description de l'assert est garantie d'être en phase avec le code.

Inconvénient:

  • Sera légèrement plus lent que ci-dessus
  • A besoin de Mockito pour travailler
  • Guère utile à autre chose que l'usecase ci-dessus.

Cependant, cela montre un moyen de le faire.

 7
Author: yankee, 2016-06-04 14:05:49

Bien que je ne l'ai pas essayé moi-même, je pense que la réponse est "non", car une référence de méthode est sémantiquement la même que celle d'un lambda.

 5
Author: Matt Ball, 2013-11-07 19:51:12

Si vous pouvez faire en sorte que l'interface Action étende Serializable, alors cette réponse d'une autre question semble fournir une solution (au moins sur certains compilateurs et runtimes).

 3
Author: user102008, 2017-05-23 11:47:24

, Nous avons publié la petite bibliothèque de.cronn: reflection-util qui peut être utilisé pour capturer un nom de méthode.

Exemple:

class MyClass {

    public void myMethod() {
    }

}

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

Détails d'implémentation: Une sous-classe Proxy de MyClass est créée avec ByteBuddy et un appel à la méthode est capturé pour récupérer son nom. ClassUtils met en cache les informations de sorte que nous n'avons pas besoin de créer un nouveau proxy à chaque invocation.

Veuillez noter que cette approche ne fonctionne pas avec static méthodes.

 2
Author: Benedikt Waldvogel, 2017-11-15 08:40:34

Essayez ceci

Thread.currentThread().getStackTrace()[2].getMethodName();
 0
Author: Asghar Nemati, 2017-07-26 14:55:13

Il peut ne pas y avoir de moyen fiable, mais dans certaines circonstances:

  1. votre MyClass n'est pas final, et a un constructeur accessible (limitation de cglib)
  2. votre myMethod n'est pas surchargé, et non pas statique

Vous pouvez essayer d'utiliser cglib pour créer un proxy de MyClass, puis en utilisant un MethodInterceptor pour signaler le Method pendant que la référence de méthode est appelée lors d'un essai suivant.

Exemple de code:

public static void main(String[] args) {
    Method m = MethodReferenceUtils.getReferencedMethod(ArrayList.class, ArrayList::contains);
    System.out.println(m);
}

Vous verrez ce qui suit sortie:

public boolean java.util.ArrayList.contains(java.lang.Object)

Tandis que:

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

Dans le code ci-dessus, MethodRefWith1Arg est juste un sucre de syntaxe pour vous permettre de référencer une méthode non statique avec un argument. Vous pouvez créer jusqu'à MethodRefWithXArgs pour référencer vos autres méthodes.

 0
Author: vivimice, 2017-11-17 05:38:34

Donc, je joue avec ce code

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

La sortie de ce code est:

index = 30, method = public void Main.test(java.lang.String)

Et comme je le remarque, l'index de la méthode référencée est toujours 30. Le code final peut ressembler à

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.");
    }

Soyez prudent avec ce code en production!

 -1
Author: Dmitriy V., 2017-11-20 18:08:49

Vous pouvez utiliser ma bibliothèque Reflect Without String

Method myMethod = ReflectWithoutString.methodGetter(MyClass.class).getMethod(MyClass::myMethod);
 -1
Author: Dean Xu, 2018-04-24 09:33:11