Printing debug info on errors with java 8 lambda expressions

What about that?

Source code:

package test;

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

public class Person {
    public String getName() {
        return "Michael";
    }

    public static void main(String[] args) throws
            NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Serializable s = (Function<Person, String> & Serializable) Person::getName;

        Method method = s.getClass().getDeclaredMethod("writeReplace");
        method.setAccessible(true);
        SerializedLambda serializedLambda = (SerializedLambda) method.invoke(s);

        System.out.println(serializedLambda.getImplClass().replace("/", ".")
                + "::" + serializedLambda.getImplMethodName());
    }
}

Output:

test.Person::getName

You actually need to look up the method "writeReplace", call it and evaluate its return value which is of type SerializedLambda.


In case you expect method references as the only input, you can debug them to printable names with the following trick:

public static void main(String[] args) {
  Person p = new Person();
  Supplier<String> nameSupplier1 = () -> "MyName";
  Supplier<String> nameSupplier2 = () -> { throw new RuntimeException(); };
  set(p, Person::setName, nameSupplier1);
  System.out.println(p.getName()); // prints MyName
  set(p, Person::setName, nameSupplier2); // throws exception with message
  System.out.println(p.getName()); // Does not execute
}

interface DebuggableBiConsumer<A, B> extends BiConsumer<A, B>, Serializable {}

private static <E, V> void set(
    E o, DebuggableBiConsumer<E, V> setter, Supplier<V> valueSupplier) {
  try {
    setter.accept(o, valueSupplier.get());
  } catch (RuntimeException e) {
    throw new RuntimeException("Failed to set the value of "+name(setter), e);
  }
}

private static String name(DebuggableBiConsumer<?, ?> setter) {
  for (Class<?> cl = setter.getClass(); cl != null; cl = cl.getSuperclass()) {
    try {
      Method m = cl.getDeclaredMethod("writeReplace");
      m.setAccessible(true);
      Object replacement = m.invoke(setter);
      if(!(replacement instanceof SerializedLambda))
        break;// custom interface implementation
      SerializedLambda l = (SerializedLambda) replacement;
      return l.getImplClass() + "::" + l.getImplMethodName();
    }
    catch (NoSuchMethodException e) {}
    catch (IllegalAccessException | InvocationTargetException e) {
      break;
    }
  }
  return "unknown property";
}

The limitations are that it will print not very useful method references for lambda expressions (references to the synthetic method containing the lambda code) and "unknown property" for custom implementations of the interface.