How to Get the Methodinfo of a Java 8 Method Reference

How to get the MethodInfo of a Java 8 method reference?

No, there is no reliable, supported way to do this. You assign a method reference to an instance of a functional interface, but that instance is cooked up by LambdaMetaFactory, and there is no way to drill into it to find the method you originally bound to.

Lambdas and method references in Java work quite differently than delegates in C#. For some interesting background, read up on invokedynamic.

Other answers and comments here show that it may currently be possible to retrieve the bound method with some additional work, but make sure you understand the caveats.

Is it possible (how) to get the name of a method reference at Runtime Java?

As you're saying that you only need this for debugging purposes, here is a trick (i.e. a dirty hack) that will allow you to do what you want.

First of all, your functional interface must be Serializable:

@FunctionalInterface
public interface FooInterface extends Serializable {

void method();
}

Now, you can use this undocumented, internal-implementation-dependent and extremely risky code to print some information about the method reference targeted to your FooInterface functional interface:

@FunctionalInterface
public interface FooInterface extends Serializable {

void method();

default String getName() {
try {
Method writeReplace = this.getClass().getDeclaredMethod("writeReplace");
writeReplace.setAccessible(true);
SerializedLambda sl = (SerializedLambda) writeReplace.invoke(this);
return sl.getImplClass() + "::" + sl.getImplMethodName();
} catch (Exception e) {
return null;
}
}
}

When you call this method:

doStuff(Foo::aMethodReference);

You'll see the following output:

package/to/the/class/Foo::aMethodReference

Note 1: I've seen this approach in this article by Peter Lawrey.

Note 2: I've tested this with openjdk version "11" 2018-09-25 and also with java version "1.8.0_192".

How to get the MethodInfo of a Java 14 method reference?

Getting a method info from a method reference never was a goal on the JDK developer’s side, so no effort was made to change the situation.

However, the approach shown in your link can be simplified. Instead of serializing the information, patching the serialized data, and restoring the information using a replacement object, you can simply intercept the original SerializedLambda object while serializing.

E.g.

public class GetSerializedLambda extends ObjectOutputStream {
public static void main(String[] args) { // example case
var lambda = (Consumer<String[]>&Serializable)GetSerializedLambda::main;
SerializedLambda sl = GetSerializedLambda.get(lambda);
System.out.println(sl.getImplClass() + " " + sl.getImplMethodName());
}

private SerializedLambda info;

GetSerializedLambda() throws IOException {
super(OutputStream.nullOutputStream());
super.enableReplaceObject(true);
}

@Override protected Object replaceObject(Object obj) throws IOException {
if(obj instanceof SerializedLambda) {
info = (SerializedLambda)obj;
obj = null;
}
return obj;
}

public static SerializedLambda get(Object obj) {
try {
GetSerializedLambda getter = new GetSerializedLambda();
getter.writeObject(obj);
return getter.info;
} catch(IOException ex) {
throw new IllegalArgumentException("not a serializable lambda", ex);
}
}
}

which will print GetSerializedLambda main. The only newer feature used here, is the OutputStream.nullOutputStream() to drop the written information immediately. Prior to JDK 11, you could write into a ByteArrayOutputStream and drop the information after the operation which is only slightly less efficient. The example also using var, but this is irrelevant to the actual operation of getting the method information.

The limitations are the same as in JDK 8. It requires a serializable method reference. Further, there is no guaranty that the implementation will map to a method directly. E.g., if you change the example’s declaration to public static void main(String... args), it will print something like lambda$1 when being compiled with Eclipse. When also changing the next line to var lambda = (Consumer<String>&Serializable)GetSerializedLambda::main;, the code will always print a synthetic method name, as using a helper method is unavoidable. But in case of javac, the name is rather something like lambda$main$f23f6912$1 instead of Eclipse’s lambda$1.

In other words, you can expect encountering surprising implementation details. Do not write applications relying on the availability of such information.

How can I find the target of a Java8 method reference?

Using the trick from this SO post you can find the target. The important method below is findTarget. As it turns out, lambdas do indeed capture their targets, and you can access them from the SerializedLambda.

However, this is a pretty nasty reflection hack and it's likely to break in future versions. I do not condone its usage.

import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Optional;
import java.util.function.Function;

public class FindMethodReferenceTarget {
public static void main(String[] args) {
String s = "123";
Optional<Object> target = findTarget(s::charAt);
System.out.println(target.get().equals(s));

Object o = new FindMethodReferenceTarget();
target = findTarget(o::equals);
System.out.println(target.get().equals(o));
}

private static <T, R> Optional<Object> findTarget(
DebuggableFunction<T, R> methodReference) {
return getLambda(methodReference).map(l -> l.getCapturedArg(0));
}

private static Optional<SerializedLambda> getLambda(Serializable lambda) {
for (Class<?> cl = lambda.getClass(); cl != null; cl = cl.getSuperclass()) {
try {
Method m = cl.getDeclaredMethod("writeReplace");
m.setAccessible(true);
Object replacement = m.invoke(lambda);
if (!(replacement instanceof SerializedLambda)) {
break; // custom interface implementation
}
SerializedLambda l = (SerializedLambda) replacement;
return Optional.of(l);
} catch (NoSuchMethodException e) {
// do nothing
} catch (IllegalAccessException | InvocationTargetException e) {
break;
}
}

return Optional.empty();
}

@FunctionalInterface
private static interface DebuggableFunction<T, R> extends
Serializable,
Function<T, R> {}
}

Get method object with method references

Short answer: No.

You will get a lambda of that method, not a java.lang.reflect.Method. You do not know the name of the method. Just as you can not have a reference to a "property" of a java bean.
You can have a reference to the getter or setter but that is also a lambda and you do not know the actual name.
In any case you'd have to provide the name as a String and that can't be checked by the compiler. I also tried this but failed. It simply can't be done unless you write something that checks the javacode/bytecode. But there are tools that do that.
Maybe the Criteria API could be used for that, but it depends on the requirements.
http://docs.oracle.com/javaee/6/tutorial/doc/gjitv.html
There you'd have a SingularAttribute or similar field on a "metamodel" and then the regular java compiler can check the (generic) type of it.

Java 8 method references : validation of methods at compile time

You seem to be trying to use method references, which are really the short-hands for lambda expressions, as method literals, which are the syntactic references to methods (much like Foo.class is the syntactic reference to class instance of Foo). These two are not the same, and this is the reason for the impedance you encounter. Things you try are the abuse of language feature which javac compiler utterly resists.

Unfortunately, there is no method literals in Java, so you will have to describe the method by other means, e.g. Reflection, MethodHandles.Lookup, etc. I think it is very easy to come up with the reflective checker for this kind of thing, or even build up the annotation processor to check the existence of given methods in compile time.



Related Topics



Leave a reply



Submit