webentwicklung-frage-antwort-db.com.de

Wie erhalte ich die MethodInfo einer Java 8-Methodenreferenz?

Bitte schauen Sie sich den folgenden Code an:

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

Dies funktioniert zwar, aber der Methodenname wird als String übergeben, sodass er auch kompiliert werden kann, wenn myMethod nicht vorhanden ist.

Auf der anderen Seite führt Java 8 ein Methodenreferenzmerkmal ein. Es wird zur Kompilierzeit geprüft. Es ist möglich, diese Funktion zu verwenden, um Methodeninformationen abzurufen.

printMethodName(MyClass::myMethod);

Vollständiges Beispiel:

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

Mit anderen Worten möchte ich einen Code haben, der dem folgenden C # -Code entspricht:

    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

Nein, dafür gibt es keine zuverlässige, unterstützte Methode. Sie ordnen einer Instanz einer funktionalen Schnittstelle eine Methodenreferenz zu. Diese Instanz wird jedoch von LambdaMetaFactory erstellt. Es gibt keine Möglichkeit, sie zu untersuchen, um die Methode zu finden, an die Sie ursprünglich gebunden haben.

Lambdas und Methodenreferenzen in Java arbeiten ganz anders als Delegierte in C #. Lesen Sie für einige interessante Hintergrundinformationen invokedynamic.

Andere Antworten und Kommentare zeigen, dass es derzeit möglicherweise möglich ist, die gebundene Methode mit einigem Aufwand abzurufen, aber stellen Sie sicher, dass Sie die Vorbehalte verstehen.

22
Mike Strobel

Sieht so aus, als könnte es doch möglich sein, dass mit einem Proxy aufgezeichnet wird, welche Methode aufgerufen wird.

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

14
ddan

In meinem Fall war ich auf der Suche nach einem Weg, um dies in Unit-Tests loszuwerden:

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

Wie Sie sehen, testet jemand die Methode getAPoint und prüft, ob die Koordinaten wie erwartet sind, aber in der Beschreibung wurde jeder Assert kopiert und ist nicht synchron mit dem, was geprüft wird. Besser wäre es, dies nur einmal zu schreiben.

Aus den Ideen von @ddan habe ich eine Proxy-Lösung mit Mockito erstellt:

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

Nein, ich kann es einfach benutzen

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

und die Beschreibung der Zusicherung ist garantiert mit dem Code synchron.

Nachteil:

  • Wird etwas langsamer als oben sein
  • Benötigt Mockito zur Arbeit
  • Kaum nützlich für etwas anderes als die oben genannte Verwendung.

Es zeigt jedoch einen Weg, wie es gemacht werden könnte.

7
yankee

Obwohl ich es selbst nicht ausprobiert habe, denke ich, die Antwort ist "nein", da eine Methodenreferenz semantisch mit einem Lambda identisch ist.

5
Matt Ball

Wenn Sie die Schnittstelle Action erweitern Serializable festlegen können, scheint diese Antwort aus einer anderen Frage eine Lösung zu bieten (zumindest bei einigen Compilern und Runtimes).

3
user102008

Wir haben die kleine Bibliothek de.cronn: reflection-util veröffentlicht, mit der ein Methodenname erfasst werden kann.

Beispiel:

class MyClass {

    public void myMethod() {
    }

}

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

Details zur Implementierung: Eine Proxy-Unterklasse von MyClass wird mit ByteBuddy erstellt, und ein Aufruf der Methode wird erfasst, um den Namen abzurufen. Die Variable_ClassUtils speichert die Informationen im Cache, sodass nicht bei jedem Aufruf ein neuer Proxy erstellt werden muss .

Bitte beachten Sie, dass dieser Ansatz nicht mit static -Methoden funktioniert.

2

Es kann keinen zuverlässigen Weg geben, aber unter bestimmten Umständen:

  1. ihr MyClass ist nicht final und hat einen zugänglichen Konstruktor (Einschränkung von cglib)
  2. ihr myMethod ist nicht überladen und nicht statisch

Sie können versuchen, mit cglib einen Proxy von MyClass zu erstellen, und dann mit MethodInterceptor den Method melden, während die Methodenreferenz in einem folgenden Testlauf aufgerufen wird.

Beispielcode:

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

Sie sehen die folgende Ausgabe:

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

Während:

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

Im obigen Code ist MethodRefWith1Arg nur ein Syntaxzucker, mit dem Sie auf eine nicht statische Methode mit einem Argument verweisen können. Sie können so viele wie MethodRefWithXArgs erstellen, um auf Ihre anderen Methoden zu verweisen.

1
vivimice

Sie können Sicherheitsspiegel zu Ihrem Klassenpfad hinzufügen und folgendermaßen vorgehen:

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

Die Bibliothek bietet auch eine getName(...)-Methode an:

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

Die Bibliothek basiert auf der Antwort von Holger: https://stackoverflow.com/a/21879031/6095334

Edit: Die Bibliothek hat verschiedene Mängel, die mir langsam bewusst werden ... __ Siehe fx Holgers Kommentar hier: Wie erhalte ich den Namen der Methode, die sich aus einem Lambda ergibt

1
Hervian

Sie können meine Bibliothek verwenden Reflect Without String

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