S'il vous plaît jeter un oeil sur le code suivant:
Method methodInfo = MyClass.class.getMethod("myMethod");
Cela fonctionne, mais le nom de la méthode est transmis sous forme de chaîne. Ainsi, cela compilera même si myMethod n'existe pas.
Par ailleurs, Java 8 introduit une fonctionnalité 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, j'aimerais avoir un code qui soit 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);
}
Non, il n'y a pas de moyen fiable et supporté de le faire. Vous affectez une référence de méthode à une instance d'une interface fonctionnelle, mais cette instance est créée par LambdaMetaFactory
et il n'existe aucun moyen de l'explorer pour trouver la méthode à laquelle vous êtes lié à l'origine.
Les Lambdas et les références de méthodes en Java fonctionnent assez différemment des délégués en C #. Pour des informations intéressantes, lisez invokedynamic
.
D'autres réponses et commentaires montrent qu'il est actuellement possible de récupérer la méthode liée avec un travail supplémentaire, mais assurez-vous de bien comprendre les mises en garde.
Cela semble être possible après tout, en utilisant un proxy pour enregistrer quelle méthode est appelée.
Dans mon cas, je cherchais un moyen de m'en débarrasser lors de tests unitaires:
Point p = getAPoint();
assertEquals(p.getX(), 4, "x");
assertEquals(p.getY(), 6, "x");
Comme vous pouvez le voir, une personne teste la méthode getAPoint
et vérifie que les coordonnées sont conformes aux attentes, mais la description de chaque assertion a été copiée et ne correspond pas à ce qui est vérifié. Mieux serait d'écrire ceci une seule fois.
A 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 il est garanti que la description de l'assertion est en phase avec le code.
Inconvénient:
Cependant, cela montre comment cela pourrait être fait.
Bien que je n’aie pas essayé moi-même, je pense que la réponse est "non", car une référence de méthode est sémantiquement identique à un lambda.
Si vous pouvez rendre l'interface Action
extend Serializable
, alors cette réponse d'une autre question semble fournir une solution (au moins sur certains compilateurs et certains environnements d'exécution).
Il peut ne pas y avoir de moyen fiable, mais dans certaines circonstances:
MyClass
n'est pas final et a un constructeur accessible (limitation de cglib)myMethod
n'est pas surchargé et non statiqueVous pouvez essayer d'utiliser cglib pour créer un proxy de MyClass
, puis d'utiliser un MethodInterceptor
pour signaler le Method
pendant que la référence à la méthode est appelée dans un prochain essai.
Exemple de code:
public static void main(String[] args) {
Method m = MethodReferenceUtils.getReferencedMethod(ArrayList.class, ArrayList::contains);
System.out.println(m);
}
Vous verrez la sortie suivante:
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 simplement un sucre de syntaxe pour vous permettre de référencer une méthode non statique avec un argument. Vous pouvez créer autant que MethodRefWithXArgs
pour référencer vos autres méthodes.
Vous pouvez ajouter safety-mirror à votre chemin de classe et procéder comme ceci:
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
La bibliothèque propose également une méthode getName(...)
:
assertEquals("isEmpty", Types.getName(String::isEmpty));
La bibliothèque est basée sur la réponse de Holger: https://stackoverflow.com/a/21879031/6095334
Edit: La bibliothèque a plusieurs défauts dont je prends lentement conscience ... .. Voir le commentaire de fx Holger ici: Comment obtenir le nom de la méthode résultant d'un lambda
Vous pouvez utiliser ma bibliothèque Reflect Without String
Method myMethod = ReflectWithoutString.methodGetter(MyClass.class).getMethod(MyClass::myMethod);