Bitte schauen Sie sich den folgenden Code an:
Method methodInfo = MyClass.class.getMethod("myMethod");
Dies funktioniert, aber der Methodenname wird als Zeichenfolge übergeben, sodass dies auch dann kompiliert wird, wenn myMethod nicht vorhanden ist.
Auf der anderen Seite führt Java 8 eine Methodenreferenzfunktion ein. Es wird zur Kompilierungszeit überprüft. Ist es 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, ich hätte gerne einen Code, 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);
}
java
reflection
lambda
java-8
Rafal
quelle
quelle
Antworten:
Nein, es gibt keine zuverlässige und unterstützte Möglichkeit, dies zu tun. Sie weisen einer Instanz einer Funktionsschnittstelle einen Methodenverweis zu, der jedoch von dieser Instanz zusammengestellt wird
LambdaMetaFactory
, und es gibt keine Möglichkeit, einen Drill-Drill durchzuführen, um die Methode zu finden, an die Sie ursprünglich gebunden waren.Lambdas und Methodenreferenzen in Java funktionieren ganz anders als Delegaten in C #. Weitere interessante Hintergrundinformationen finden Sie unter
invokedynamic
.Andere Antworten und Kommentare hier zeigen, dass es derzeit möglicherweise möglich ist, die gebundene Methode mit einigen zusätzlichen Arbeiten abzurufen, aber stellen Sie sicher, dass Sie die Einschränkungen verstehen.
quelle
In meinem Fall suchte ich nach einer Möglichkeit, dies in Unit-Tests zu beseitigen:
Point p = getAPoint(); assertEquals(p.getX(), 4, "x"); assertEquals(p.getY(), 6, "x");
Wie Sie sehen können, testet jemand die Methode
getAPoint
und überprüft, ob die Koordinaten wie erwartet sind, aber in der Beschreibung jeder Behauptung wurde kopiert und ist nicht synchron mit dem, was überprüft wird. Besser wäre es, dies nur einmal zu schreiben.Aus den Ideen von @ddan habe ich mit Mockito eine Proxy-Lösung 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 einfach verwenden
assertPropertyEqual(p, Point::getX, 4); assertPropertyEqual(p, Point::getY, 6);
und die Beschreibung der Zusicherung ist garantiert mit dem Code synchron.
Nachteil:
Es zeigt jedoch einen Weg, wie es gemacht werden könnte.
quelle
String getMethod()
in ihren Mocks zu implementieren )Obwohl ich es selbst nicht ausprobiert habe, denke ich, dass die Antwort "Nein" ist, da eine Methodenreferenz semantisch mit einem Lambda identisch ist.
quelle
Wenn Sie die Schnittstelle
Action
erweitern könnenSerializable
, scheint diese Antwort auf eine andere Frage eine Lösung zu bieten (zumindest bei einigen Compilern und Laufzeiten).quelle
Es gibt möglicherweise keinen zuverlässigen Weg, aber unter bestimmten Umständen:
MyClass
ist nicht endgültig und hat einen zugänglichen Konstruktor (Einschränkung von cglib)myMethod
ist nicht überlastet und nicht statischSie können versuchen, mit cglib einen Proxy von zu erstellen
MyClass
und dann mit aMethodInterceptor
zu melden,Method
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
MethodRefWith1Arg
ist dies nur ein Syntaxzucker, mit dem Sie auf eine nicht statische Methode mit einem Argument verweisen können. Sie können so viele erstellen, wie SieMethodRefWithXArgs
auf Ihre anderen Methoden verweisen.quelle
Sie können Ihrem Klassenpfad einen Sicherheitsspiegel hinzufügen und dies folgendermaßen tun:
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:assertEquals("isEmpty", Types.getName(String::isEmpty));
Die Bibliothek basiert auf Holgers Antwort: https://stackoverflow.com/a/21879031/6095334
Bearbeiten: Die Bibliothek hat verschiedene Mängel, die mir langsam bewusst werden. Siehe fx Holgers Kommentar hier: So erhalten Sie den Namen der Methode, die aus einem Lambda resultiert
quelle
Wir haben das Reflection-Util für kleine Bibliotheken veröffentlicht, mit dem 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"
Implementierungsdetails:
MyClass
Mit ByteBuddy wird eine Proxy-Unterklasse von erstellt und ein Aufruf der Methode erfasst, um ihren Namen abzurufen.ClassUtils
speichert die Informationen so zwischen, dass wir nicht bei jedem Aufruf einen neuen Proxy erstellen müssen.Bitte beachten Sie, dass dieser Ansatz mit statischen Methoden nicht funktioniert .
quelle
Also spiele ich mit diesem 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(); } }
Ausgabe dieses Codes ist:
index = 30, method = public void Main.test(java.lang.String)
Und wie ich bemerke, ist der Index der referenzierten Methode immer 30. Der endgültige Code kann so aussehen
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."); }
Seien Sie vorsichtig mit diesem Code in der Produktion!
quelle
Du kannst meine Bibliothek benutzen
Reflect Without String
quelle
Versuche dies
Thread.currentThread().getStackTrace()[2].getMethodName();
quelle