Java – In Java 8, is it stylistically better to use method reference expressions or methods returning an implementation of the functional interface

coding-stylejavalambdamethodsnaming

Java 8 added the concept of functional interfaces, as well as numerous new methods that are designed to take functional interfaces. Instances of these interfaces can be succinctly created using method reference expressions (e.g. SomeClass::someMethod) and lambda expressions (e.g. (x, y) -> x + y).

A colleague and I have differing opinions on when it is best to use one form or another (where "best" in this case really boils down to "most readable" and "most in line with standard practices", as they're basically otherwise equivalent). Specifically, this involves the case where the following are all true:

  • the function in question isn't used outside a single scope
  • giving the instance a name helps readability (as opposed to e.g. the logic being simple enough to see what's happening at a glance)
  • there aren't other programming reasons why one form would be preferable to the other.

My current opinion on the matter is that adding a private method, and referring that by method reference, is the better approach. It feels like this is how the feature was designed to be used, and it seems easier to communicate what is going on via method names and signatures (e.g. "boolean isResultInFuture(Result result)" clearly is saying it's returning a boolean). It also makes the private method more reusable if a future enhancement to the class wants to make use of the same check, but doesn't need the functional interface wrapper.

My colleague's preference is to have a method which returns the instance of the interface (e.g. "Predicate resultInFuture()"). To me, this feels like it's not quite how the feature was intended to be used, feels slightly clunkier, and seems like it's harder to really communicate intent through naming.

To make this example concrete, here is the same code, written in the different styles:

public class ResultProcessor {
  public void doSomethingImportant(List<Result> results) {
    results.filter(this::isResultInFuture).forEach({ result ->
      // Do something important with each future result line
    });
  }

  private boolean isResultInFuture(Result result) {
    someOtherService.getResultDateFromDatabase(result).after(new Date());
  }
}

vs.

public class ResultProcessor {
  public void doSomethingImportant(List<Result> results) {
    results.filter(resultInFuture()).forEach({ result ->
      // Do something important with each future result line
    });
  }

  private Predicate<Result> resultInFuture() {
    return result -> someOtherService.getResultDateFromDatabase(result).after(new Date());
  }
}

vs.

public class ResultProcessor {
  public void doSomethingImportant(List<Result> results) {
    Predicate<Result> resultInFuture = result -> someOtherService.getResultDateFromDatabase(result).after(new Date());

    results.filter(resultInFuture).forEach({ result ->
      // Do something important with each future result line
    });
  }
}

Are there any official or semi-official documentation or comments around whether one approach is more preferred, more in-line with the language designers' intents, or more readable? Barring an official source, are there any clear reasons why one would be the better approach?

Best Answer

In terms of functional programming, what you and your colleague are discussing is point free style, more specifically eta reduction. Consider the following two assignments:

Predicate<Result> pred = result -> this.isResultInFuture(result);
Predicate<Result> pred = this::isResultInFuture;

These are operationally equivalent, and the first is called pointful style while the second is point free style. The term "point" refers to a named function argument (this comes from topology) which is missing in the latter case.

More generally, for all functions F, a wrapper lambda that takes all arguments given and passes them to F unchanged, then returns the result of F unchanged is identical to F itself. Removing the lambda and using F directly (going from the pointful style to the point-free style above) is called an eta reduction, a term that stems from lambda calculus.

There are point free expressions that are not created by eta reduction, the most classic example of which is function composition. Given a function from type A to type B and a function from type B to type C, we can compose them together into a function from type A to type C:

public static Function<A, C> compose(Function<A, B> first, Function<B, C> second) {
  return (value) -> second(first(value));
}

Now suppose we have a method Result deriveResult(Foo foo). Since a Predicate is a Function, we can construct a new predicate that first calls deriveResult and then tests the derived result:

Predicate<Foo> isFoosResultInFuture =
    compose(this::deriveResult, this::isResultInFuture);

Although the implementation of compose uses pointful style with lambdas, the use of compose to define isFoosResultInFuture is point free because no arguments need to be mentioned.

Point free programming is also called tacit programming, and it can be a powerful tool to increase readability by removing pointless (pun intended) details from a function definition. Java 8 doesn't support point free programming nearly as thoroughly as more functional languages like Haskell do, but a good rule of thumb is to always perform eta-reduction. There's no need to use lambdas that have no different behavior from the functions they wrap.

Related Topic