Lambda expressions are widely used in functional programming, but they are difficult to read and understand. In many cases, lambda expressions exist simply to pass one or more parameters, and are best replaced with method references. In this article, you’ll learn how to identify transition-lambda expressions in your code and how to replace them with corresponding method references. The use of method references requires learning, but the long-term benefits will outweigh the effort.

What is a passing lambda expression?

Lambda expressions are often passed as anonymous functions in functional programming, using lambda as arguments to higher-order functions.

Example We pass a lambda expression to the filter method:

public class LambdaDemo {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		List<Integer> numbers = Arrays.asList(1.2.3.4.5.6.7.8.9.10);
		numbers.stream()
		.filter(e -> e % 2= =0) .forEach(e -> System.out.println(e)); }}Copy the code

In this code, we pass a lambda expression to the forEach method. Although the two lambda expressions clearly do different things, there is another important nuance between them: the first lambda expression does actual work, while the second does not. The lambda expression passed to the forEach method is what we call a passed lambda expression. The expression ““e -> system.out.println (e) passes its parameter as an actual parameter to the println method. There is nothing wrong with this expression, but its syntax is too complex for this task. To understand what (parameters -> body ‘ ‘is for, we need to go to body (on the right side of the expression) to see what happens to the parameter. If the lambda expression does not perform any operations on the parameter, the effort is wasted.

Replacing a lambda expression with a method reference is beneficial in this case. Unlike method calls, method references refer to methods by which we pass parameters. Using method references also gives rise to a variety of parameter passing methods.

Rewrite the previous code to pass parameters by method reference:

public static void main(String[] args) {
		// TODO Auto-generated method stub
		List<Integer> numbers = Arrays.asList(1.2.3.4.5.6.7.8.9.10);
		numbers.stream()
		.filter(e -> e % 2= =0)
		.forEach(System.out::println);
	}
Copy the code

The result is the same as in the above example. Using method references reduces the effort of understanding the code, and the benefits multiply as you write and read more code.

Pass parameters as arguments

Next we’ll look at variations that pass lambda expressions, showing you how to replace each expression with a method reference.

An argument to the instance method

It is quite common for lambda expressions to pass their parameters as arguments to instance methods. In the example above, the parameter e is passed as an argument to the println method. In the distorted version we replace the lambda expression with a method reference to System.out::println.

The following figure shows lambda expressions passing parameters as arguments to instance methods:

If you are not familiar with method references, looking at such a lambda expression can help you understand where its structure and parameters are passed. To change a lambda expression to a method reference, simply remove the generic part (parameters and arguments) and replace the dot with a colon on the method call.

A method argument to this

One special case of passing an expression in the previous example is when instance methods are called on the context instance of the current method. Suppose we have a class called Example that contains an instance method increment:

public class Example {
  public int increment(int number) {
    return number + 1;
  }

  / /...
}
Copy the code

Now suppose we have another instance method where we create a lambda expression and pass it to the Stream’s map method as follows:

.map(e -> increment(e))
Copy the code

This may not be obvious, but the code structure is very similar to the previous example. We all pass parameters as arguments to the instance method. Rewrite this code slightly to make the similarity more obvious:

.map(e -> this.increment(e))

Copy the code

By introducing this as a call to increment, the structure change of the expression is made clearer. Let’s rewrite this code again as a method reference:

.map(this::increment)
Copy the code

Very similar to replacing e -> system.out.println (e) with system.out ::println, We can replace the lambda expression e -> increment(e) (or, more accurately, e -> this.increment(e)) with this::increment. In both cases, the code is clearer.

Arguments to static methods

In the above example we substituted a lambda expression that passed a parameter as an argument to the instance method. You can also replace lambda expressions that pass parameters to static methods.

Pass the parameter to the static method:

.map(e -> Integer.valueOf(e))
Copy the code

In this example, we pass the lambda expression’s parameters as arguments to the valueOf method of the Integer class. The difference is that in this example, the method being called is a static method rather than an instance method. As before, we replace this example with a method reference, but note the detail that the method reference is placed not on the instance, but on a class.

Method references to static methods:

.map(Integer::valueOf)
Copy the code

To summarize: If the purpose of a lambda expression is simply to pass a parameter to an instance method, it can be replaced with a method reference on the instance. If a pass expression is passed to a static method, it can be replaced with a method reference on the class.

Pass the parameter to the target

You might use the ClassName::methodName format in two different scenarios. The first format, seen above, has parameters that are passed to static methods as arguments. Now let’s consider one variation: the parameter is the target of the method call.

Using parameters as targets:

.map(e -> e.doubleValue())
Copy the code

Here is the structure of such lambda expressions:

Ambiguity and method references

Looking at method references, it is not easy to determine whether the parameter is passed to a static method or used as a target. To understand the difference, we need to know whether the method is static or instance. This is not that important from a code readability point of view, but knowing this difference is critical to successful compilation.

If a static method of a class has the same name as a compatible instance method, and we use a method reference, the compiler will consider the call ambiguous. So, for example, we cannot replace the lambda expression (Integer e) -> e.tostring () with a method reference to Integer::toString, Because the Integer class contains both the static method public static String toString(int I) and the instance method public String toString().

You or your IDE might suggest using Object::toString to solve this particular problem, because there is no statictoString method in Object. Although the solution compiles, this cleverness is usually unhelpful. You must be able to confirm that the method reference is calling the desired method. When in doubt, it is best to use lambda expressions to avoid any confusion or possible errors.

Pass the constructor call

In addition to static and instance methods, method references can also be used to represent calls to constructors. Consider the constructor call issued from Supplier, which Supplier provides as an argument to the toCollection method.

An example constructor call:

.collect(toCollection(() -> new LinkedList<Double>()));
Copy the code

The purpose of the code is to take a Stream of data and refine or collect it into a LinkedList. The toCollection method accepts a Supplier as an actual parameter. Supplier does not accept any parameters, so () is empty. It returns a Collection instance, which in this case is LinkedList.

From parameters to constructor arguments:

The received parameters, which may be empty, are passed to the constructor as arguments. In the following example, we can replace a lambda expression with a method reference to new.

Replace the constructor with a method reference:

.collect(toCollection(LinkedList::new));
Copy the code

This example contains method references much more succinctly than the original code containing lambda expressions and is therefore easier to understand.

Pass multiple arguments

We have already seen an example of passing 0 more parameters. But Rahm expressions are not limited to zero or one parameter; they also apply to multiple arguments.

Execute the lambda expression to reduce():

.reduce(0, (total, e) -> Integer.sum(total, e)));
Copy the code

The reduce method is called on the Stream and the sum method of Integer is used to sum the values in the Stream. The lambda expression in this example takes two parameters that are passed to the sum method as arguments (in exactly the same order). Figure 4 shows the structure of the lambda expression.

Pass two parameters as arguments:

You can also replace this Lamm expression with a method reference:

.reduce(0, Integer::sum));
Copy the code

Passed as targets and arguments

Instead of passing all arguments to static methods as arguments, lambda expressions can take one parameter as the target of instance method calls. If the first parameter is used as a target, the lambda expression can be replaced with a method reference.

Execute reduce() on lambda expressions that take parameters as targets:

.reduce("", (result, letter) -> result.concat(letter)));
Copy the code

In this example, the reduce method is called on Stream. The lambda expression concatenates strings using the concat instance method of String. The transfer structure in this lambda expression is different from the structure you saw in the previous Reduce example:

The first parameter of a lambda expression is used as the target of the instance method call. The second parameter is used as an argument to the method. In this order, the lambda expression can be replaced with a method reference.

.reduce("", String::concat));
Copy the code

Notice that although the lambda expression calls an instance method, you are using the class name again. In other words, method references look the same whether you call static methods or instance methods with the first parameter as the target. As long as there is no ambiguity, there is no problem.

It is best to use method references

It takes time and effort to master the distortions and structures that pass lambda expressions, as well as the method references that replace them. Once you understand it, it starts to feel more natural to use method references instead of passing expressions.

Better than lambda expressions, method references make your code very concise and expressive, which greatly reduces the effort of reading code. Let’s look at the following example.

Examples of lambda expressions:

List<String> nonNullNamesInUpperCase =
    names.stream()
      .filter(name -> Objects.nonNull(name))
      .map(name -> name.toUpperCase())
      .collect(collectingAndThen(toList(), list -> Collections.unmodifiableList(list)));
Copy the code

Given a List names, the code above removes all null values from the List, converts each name to uppercase, and collects the results into an unmodifiable List.

Now let’s rewrite the above code using method references. In this case, each lambda expression is a transitive expression, either to a static or instance method. Therefore, we replace each lambda expression with a method reference:

List<String> nonNullNamesInUpperCase =
    names.stream()
      .filter(Objects::nonNull)
      .map(String::toUpperCase)
      .collect(collectingAndThen(toList(), Collections::unmodifiableList));
Copy the code

Comparing the two listings, it’s easy to see that the code that uses method references is smoother and easier to read. The idea is simple: given a name, filter non-NULL values, map them to uppercase, and collect them into an immutable list.

conclusion

Whenever you see a lambda expression whose sole purpose is to pass parameters to one or more other functions, you need to consider whether it would be better to replace that lambda expression with a method reference. The determining factor is that no real work is being done inside the lambda expression. In this case, a lambda expression is just a transitive expression, and its syntax may be too complex for the task at hand.

Once you’re comfortable with method references, you’ll find that using method references makes the same code smoother and more expressive than using lambda expressions.

Article study address:

Thank you Dr. Venkat Subramaniam

Dr Venkat Subramaniam site: http://agiledeveloper.com/

My blog

Scan the QR code to follow the wechat official account for more timely content