Exemplifying method reference vs. lambda – Functional style programming – extending API
180. Exemplifying method reference vs. lambda
Have you ever written a lambda expression and your IDE advises you to replace it with a method reference? Of course, you did! And, I’m sure that you preferred to follow the replacement because name matters, and method references are often more readable than lambdas. While this is a subjective matter, I’m pretty sure that you agree that extracting long lambdas in methods and using/re-using them via method reference is a generally accepted good practice.But, beyond some esoteric JVM internal representations, are they behaving the same? Is any difference between a lambda and a method reference that may affect how the code behaves?Well, let’s assume that we have the following simple class:
public class Printer {
Printer() {
System.out.println(“Reset printer …”);
}
public static void printNoReset() {
System.out.println(
“Printing (no reset) …” + Printer.class.hashCode());
}
public void printReset() {
System.out.println(“Printing (with reset) …”
+ Printer.class.hashCode());
}
}
If we assume that p1 is a method reference and p2 is the corresponding lambda then we can perform the following calls:
System.out.print(“p1:”);p1.run();
System.out.print(“p1:”);p1.run();
System.out.print(“p2:”);p2.run();
System.out.print(“p2:”);p2.run();
System.out.print(“p1:”);p1.run();
System.out.print(“p2:”);p2.run();
Next, let’s see two scenarios of working with p1 and p2.
Scenario 1: Call printReset()
In the first scenario, we call printReset() via p1 and p2 as follows:
Runnable p1 = new Printer()::printReset;
Runnable p2 = () -> new Printer().printReset();
If we run the code right now then we get this output (the message generated by the Printer constructor):
Reset printer …
This output is caused by the method reference, p1. The Printer constructor is invoked right away even if we didn’t call the run() method. Because p2 (the lambda) is lazy, the Printer constructor is not called until we call the run() method. Going further, we fire the chain of run() calls for p1 and p2. The output will be:
p1:Printing (with reset) …1159190947
p1:Printing (with reset) …1159190947
p2:Reset printer …
Printing (with reset) …1159190947
p2:Reset printer …
Printing (with reset) …1159190947
p1:Printing (with reset) …1159190947
p2:Reset printer …
Printing (with reset) …1159190947
If we analyze this output we notice that the Printer constructor is called each time the lambda (p2.run()) is executed. On the other hand, for the method reference (p1.run()) the Printer constructor is not called. It was called a single time, at p1 declaration. So, p1 is printing without resetting the printer. This can be a major aspect!
Scenario 2: Call static printNoReset()
Next, let’s call the static method printNoReset():
Runnable p1 = Printer::printNoReset;
Runnable p2 = () -> Printer.printNoReset();
If we run the code right away then nothing will happen (no output). Next, we fire up the run() calls, and we get this output:
p1:Printing (no reset) …149928006
p1:Printing (no reset) …149928006
p2:Printing (no reset) …149928006
p2:Printing (no reset) …149928006
p1:Printing (no reset) …149928006
p2:Printing (no reset) …149928006
The printNoReset() is a static method, so the Printer constructor is not invoked. We can interchangeably use p1 or p2 without having any difference in behavior. So, in this case, is just a matter of preference.
Conclusion
When calling non-static methods, there is a main difference between method reference and lambda. Method reference calls the constructor immediately and only once (at method invocation (run()), the constructor is not called). On the other hand, lambdas are lazy. They call the constructor only at method invocation and at each such invocation (run()).