Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I want to capture calls to a mock object

public interface Service {
    public String stringify(Object o);
}
service = mockery.mock(Service.class);
mockery.allowing(service::stringify).with(42).will(() -> "42");

So inside allowing I have a Function<Object, String>

Is there any reflecto-magic that will let me find the service from the function created from the method reference?

public WithClause allowing(Function<T,R> f) {
    Object myServiceBackAgain = findTargetOf(function);
    ....
}

I know that the Function will always come from these method references, so I'm happy to down-cast as much as is necessary.

This is not the same question as the related Is it possible to convert method reference to MethodHandle? because, well for a start it isn't the same question, just in a related area. And even if I can get a MethodHandle, I can't get the target from it.

like image 287
Duncan McGregor Avatar asked Jul 02 '15 07:07

Duncan McGregor


People also ask

Which symbol specifies method references in Java?

Method references: A primer. The lambda presents (s) as its formal parameter list and a code body whose System. out. println(s) expression prints s 's value to the standard output stream.

What is method reference and constructor references in Java 8?

A method reference is similar to lambda expression used to refer a method without invoking it while constructor reference used to refer to the constructor without instantiating the named class.


1 Answers

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> {}
}
like image 116
Jeffrey Avatar answered Sep 30 '22 16:09

Jeffrey