Printing Debug Info on Errors with Java 8 Lambda Expressions

Printing debug info on errors with java 8 lambda expressions

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.

How to debug stream().map(...) with lambda expressions?

I usually have no problem debugging lambda expressions while using Eclipse or IntelliJ IDEA. Just set a breakpoint and be sure not to inspect the whole lambda expression (inspect only the lambda body).

Debugging Lambdas

Another approach is to use peek to inspect the elements of the stream:

List<Integer> naturals = Arrays.asList(1,2,3,4,5,6,7,8,9,10,11,12,13);
naturals.stream()
.map(n -> n * 2)
.peek(System.out::println)
.collect(Collectors.toList());

UPDATE:

I think you're getting confused because map is an intermediate operation - in other words: it is a lazy operation which will be executed only after a terminal operation was executed. So when you call stream.map(n -> n * 2) the lambda body isn't being executed at the moment. You need to set a breakpoint and inspect it after a terminal operation was called (collect, in this case).

Check Stream Operations for further explanations.

UPDATE 2:

Quoting Holger's comment:

What makes it tricky here is that the call to map and the lambda
expression are in one line so a line breakpoint will stop on two
completely unrelated actions.

Inserting a line break right after map(
would allow you to set a break point for the lambda expression only.
And it’s not unusual that debuggers don’t show intermediate values of
a return statement. Changing the lambda to n -> { int result=n * 2; return result; }
would allow you to inspect result. Again, insert line
breaks appropriately when stepping line by line…

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".

Creating String representation of lambda expression

The simplest thing I could come up with is creating a "named predicate" that gives your predicates a name or description, basically anything that will be useful as a toString:

public class NamedPredicate<T> implements Predicate<T> {
private final String name;
private final Predicate<T> predicate;

public NamedPredicate(String name, Predicate<T> predicate) {
this.name = name;
this.predicate = predicate;
}

@Override
public boolean test(T t) {
return predicate.test(t);
}

@Override
public String toString() {
return name;
}

public static void main(String... args) {
Predicate<Integer> isEven = new NamedPredicate<>("isEven", i -> i % 2 == 0);
System.out.println(isEven); // prints isEven
}
}

Arguably giving your predicates names or descriptions like this makes the code where you use them a bit easier to understand also:

Stream.of(1, 2, 3, 4)
.filter(isEven)
.forEach(System.out::println);

A stranger idea might be to derive a "structural" description of the predicate, i.e. what is the output for some given inputs? Obviously this would work best when the input set is finite and small (e.g. for enums, booleans or some other restricted set), but I guess you could try a small set of "random" integers for integer predicates as well:

private static Map<Boolean, List<Integer>> testPredicate(Predicate<Integer> predicate) {
return Stream.of(-35, -3, 2, 5, 17, 29, 30, 460)
.collect(Collectors.partitioningBy(predicate));
}

For isEven, this would return something like {false=[-35, -3, 5, 17, 29], true=[2, 30, 460]}, which I don't think is necessarily clearer than if you manually give them a description, but is perhaps useful for predicates that are not under your control.

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.

How to print a FunctionA,B?

seems to be XY problem: you actually need to log errors in convenient way, but do not know how to refer class fields in code... There are two options:

  1. lombok: @FieldNameConstants - there are some issues
  2. implement annotation processor which will generate metamodel for your classes (or if you are on HBN you may use existing one: https://vladmihalcea.com/jpa-criteria-metamodel/)


Related Topics



Leave a reply



Submit