How to make method argument evaluation lazy?

Viewed 230

I have a debug log statement in my code to which I am passing a result of costly method call.

log.debug("Data - {}", obj.costlyMethodCall());

Now, even if debug logging is disabled, obj.costlyMethodCall() will always be evaluated to be passed as method argument.

One way to mitigate this is to have a explicit check if debug logging is enabled

if (log.isDebugEnabled()) {
  log.debug("Data - {}", obj.costlyMethodCall());
}

But this reduces code readability.

Is there any better way to make invocation of obj.costlyMethodCall() lazy or conditional here?

3 Answers

Usually this is solved by having a method accepting something similar to a Supplier instead of a concrete Object so that you can pass in something like

log.debug("Data - {}", () -> obj.costlyMethodCall());

where log.debug is something like

public void debug(String message, Supplier<Object> supplier) {
    Object value = supplier.get(); // costlyMethodCall is only called here
    // ...
}

Yes, you could use lamdba expression to delay that. New versions of logger use this approach, like log4j 2.4. You don't need to provide new methods, just use newer logging API if you can.

You can do the following:

log.debug("Data - {}", () -> obj.costlyMethodCall());

They added this in recent logger where you need to delay String usage, without polluting your code with the if(log.isDebugEnabled())...

You pass a Supplier that will return the Strings, only once called.

You can check this link if you need to: https://garygregory.wordpress.com/2015/09/16/a-gentle-introduction-to-the-log4j-api-and-lambda-basics/

If you are already extensively bought into a logging framework, defining your own methods, as suggested by Smutje, may not be a practical option.

Instead, define a static method something like:

static <T> Supplier<T> lazy(Supplier<? extends T> delegate) {
  return new Supplier<T>() {
    @Override public T get() { return delegate.get(); }
    @Override public String toString() { return Objects.toString(get()); }
  };
}

Then provide this as an argument to your call:

log.debug("Data - {}", lazy(() -> obj.costlyMethodCall()));

If you don't provide a Supplier which overrides toString(), the logging library can't (necessarily) know that it needs to evaluate the supplier to build the toString(). Ideone example.

Related