Favor Method References Over Lambdas

 class​ Inventory {
 
  List<Supply> supplies = ​new​ ArrayList<>();
 
 long​ countDifferentKinds() {
 return​ supplies.stream()
» .filter(supply -> !supply.isContaminated())
» .map(supply -> supply.getName())
  .distinct()
  .count();
  }
 }

Lambda expressions like you’ve seen them in the previous comparison can make your code more readable. But these benefits come at a price: you can’t execute lambda expressions partway—you can only run the whole stream. That means it’s hard to test parts of lambda expressions as you’d want to do in a unit test.

Above, you can see a slightly modified solution from the previous comparison: Favor Functional Over Imperative Style that uses a logical negation. It contains two lambda expressions, one Predicate to filter and one Function to map. We defined them inline to make the code more concise—they’re not referenced by a variable, so we can’t use them anywhere else. They’re only part of their encompassing method.

You may be wondering: why is this a problem? In very simple cases, it isn’t. But if the lambda expressions contains more logic—for example, if the Predicate is a complex condition or the Function a multi-line conversion—then there’s potential for errors. And because we can’t reference those lambda expressions, we can’t test them in isolation with unit tests to ensure that they work as expected. That’s not good.

Code that builds on method calls, on the other hand, is easier to test, because you can call the methods separately from their integration. Luckily, functional programming in Java also provides a mechanism to take care of this: method references. Using method references, you can embed method calls directly as a lambda expression, and that makes quality assurance easier.

So let’s see how can we improve the code with method references:

 class​ Inventory {
 
  List<Supply> supplies = ​new​ ArrayList<>();
 
 long​ countDifferentKinds() {
 return​ supplies.stream()
» .filter(Supply::isUncontaminated)
» .map(Supply::getName)
  .distinct()
  .count();
  }
 }

The syntax is fairly simple. Instead of defining a normal lambda expression, we can reference existing methods directly in the stream.

Take a look at the code. We’ve replaced the lambda expressions in the code snippet before, with references to methods. That way, we could even Avoid Negations! Now, the stream only orchestrates existing (and, of course, tested) methods. This combines the best of both worlds.

And it doesn’t stop there: with method references, the code gets shorter and more readable, even if you might have to get used to the syntax at first.

Method references require a special (and new) syntax of the form ClassName::methodName. For instance, Supply::getName references the getName() method of the class Supply.

Of course, the methods you reference must fit into the place where you use them. The filter operation requires a method reference that fits the Predicate interface (a method that takes an object and returns a Boolean) and the map operation one that fits the Function interface (a method that takes an object and returns an object).

At this point, you might think about converting your lambda expressions into methods. You should do that when the expression is rather complex or when you need it multiple times to avoid “lambda duplication.”

As a side note, method references are very flexible: you can even reference a constructor in the form ClassName::new. This may come in handy when turning your stream into a collection with collect(Collectors.toCollection(TreeSet::new)).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.12.166.255