Plan
- Before Java 8, we cannot pass a boolean-valued function/method as argument to another method : example of use case, consequences and workarounds
- How Java 8 addresses the need of passing a boolean-valued function/method as argument to another method ?
We will show a very simple code example of processing performed in a version pre-Java 8 where the language capacities appear as insufficient to get a flexible and readable code.
Then, we will see how Java 8 Predicates address this lack.
Before Java 8, we cannot pass a boolean-valued function/method as argument to another method :
example of use case, consequences and workarounds
Here is the Quotation class, a class representing a quotation for a stock identified by an ISIN.
The class contains some fields defining its state but also some logic methods manipulating its own fields.
package davidhxxx.basics.java8; import java.time.LocalDate; public class Quotation { private String isin; private LocalDate date; private float open; private float high; private float low; private float close; private long volume; private String currency; public Quotation(LocalDate date, float open, float high, float low, float close, Long volume) { this.date = date; this.open = open; this.high = high; this.low = low; this.close = close; this.volume = volume; } public String getIsin() { return isin; } public LocalDate getDate() { return date; } public float getOpen() { return open; } public float getHigh() { return high; } public float getLow() { return low; } public float getClose() { return close; } public long getVolume() { return volume; } public String getCurrency() { return currency; } public boolean isUpDay() { return close > open; } public boolean isDownDay() { return close < open; } public float computeAbsolutePrct() { float var = (close - open) / open; return Math.abs(var); } @Override public String toString() { return "Quotation [isin=" + isin + ", date=" + date + ", open=" + open + ", high=" + high + ", low=" + low + ", close=" + close + ", volume=" + volume + ", currency=" + currency + "]"; } } |
The field of trading analysis tools provides generally a trend detection feature.
This feature may have as source some computations done on a set of quotations.
Suppose we want to to perform these computations.
Today, we decide that we need to count the number of up days (close price > open price)
for some quotations.
Here is a simple method that does the job :
public static int computeNbUpDay(List<Quotation> quotations) { int nbDay = 0; for (Quotation q : quotations) { if (q.isUpDay()) { nbDay += 1; } } return nbDay; } |
This first count is useful but it is not enough to compute a trend.
Now, we also want to have a way to count the number of down days (close price < open price)
.
Here is a new method, that looks like finally as a very slightly modified version of the last one :
public static int computeNbDownDay(List<Quotation> quotations) { int nbDay = 0; for (Quotation q : quotations) { if (q.isDownDay()) { nbDay += 1; } } return nbDay; } |
It is annoying as we duplicate most of statements in the new method while only one statement differs : the condition to increment the nbDay variable.
A single method with duplication is not the hell. So, go on.
At last, we decide that we also need a way to count the number of days both up (close price > open price)
and which the absolute variation between the open and the close price is superior or equal to a parameter provided by the user.
public static int computeNbUpDay(List<Quotation> quotations, float minPrct) { int nbDay = 0; for (Quotation q : quotations) { if (q.isUpDay() && q.computeAbsolutePrct() >= minPrct) { nbDay += 1; } } return nbDay; } |
It works but we finish with three methods which the most of statements are duplicated.
And it is only the beginning. We are far from having implemented all required computations to detect a trend. We may still have dozen or hundreds of them relying on the same logical : iteration on a set of quotations and filtering the quotations according to a specific rule.
Creating so much code duplication and writing so much code line to enable simple filtering seems clumsy.
In our example, these three methods should be rather a single method and ideally we would like to pass to this generic method, a method/function that produces a boolean value.
For example, we would like to do something like that in a fancy java 7 code:
computeNbDay(quotations -> isUpDay()); computeNbDay(quotations -> isDownDay()); computeNbDay(quotations -> isUpDay() && quotation.computeAbsolutePrct() >= minPrct); |
The problem is that before Java 8, the language doesn’t provide an out of the box way to pass a method to a method.
To get an expressive way to pass something that looks like a method, we had a single way : introducing some abstractions by creating custom classes.
We could for example create a generic interface that represents a predicate and declares a single method : boolean test(T) that takes as parameter a generic object and that returns a boolean to indicate if the test condition succeeds or a fails.
public interface Predicate<T> { boolean test(T t); } |
We can now define two implementations of it : IsUpDayPredicate and IsDownDayPredicate :
public class IsUpDayPredicate implements Predicate<Quotation> { public boolean test(Quotation q){ return p.isUpDay(); } } public class IsDownDayPredicate implements Predicate<Quotation> { public boolean test(Quotation q){ return p.isDownDay(); } } |
Now, we could update our computeNbDay() method so that it takes as argument a Predicate :
public static int computeNbDay(List<Quotation> quotations, Predicate<Quotation> p) { int nbDay = 0; for (Quotation q : quotations) { if (p.test(q)) { nbDay += 1; } } return nbDay; } |
We could invoke the method in this way :
computeNbDay(quotations, new IsDownDayPredicate()); |
It works but creating a new class for each predicate is all the same annoying. Overall if we have to create dozen of them.
The class doesn’t perform many things and finally we finish with more boiler plate code than functional code.
An alternative for explicit subclassing are anonymous classes :
Predicate<Quotation> isUpDatePredicate = new Predicate<>(){ public boolean test(Quotation q){ return p.isUpDay(); } } |
We may invoke the method now without declaring a named class :
computeNbDay(quotations, new Predicate<>(){ public boolean test(Quotation q){ return p.isUpDay(); }); |
This works but it has one important limitation :
– It is verbose and not straight readable. Indeed, we have to create a new anonymous class at each time the rule is used and in a general way anonymous classes have also a bad readability.
How Java 8 addresses the need of passing a boolean-valued function/method as argument to another method ?
Java 8 addresses these limitations with a new concept and a specific functional interface: the lambdas and the java.util.function.Predicate functional interface.
This java.util.function.Predicate interface has some similarities with the custom Predicate interface introduced in our previous example but it is in fact much more flexible and powerful.
It has a great advantage : it is a functional interface. So we can use it with Java 8 lambdas.
Using the Predicate interface without lambda, that is, as we did with the pre-java 8 sample code has exactly the same limitation that we had previously : verbosity.
As other interesting and more advanced advantage, the interface provides additional methods to the test() method to provide a way to create composed predicates.
With Java 8, we could now, out of the box, declare a single computeNbDay() method that takes as parameter a Predicate typed with a Quotation.
Here is the method :
public static int computeNbDay(List<Quotation> quotations, Predicate<Quotation> p) { int nbDay = 0; for (Quotation q : quotations) { if (p.test(q)) { nbDay += 1; } } return nbDay; } |
We can now invoke the method with an inline lambda expressions to value the Predicate parameter which the functional interface waits for a boolean:
computeNbDay(quotations, q -> q.isUpDay()); computeNbDay(quotations, q -> q.isDownDay()); computeNbDay(quotations, q -> q.isUpDay() && q.computeAbsolutePrct() >= 0.2F); |
We can also replace lambda by method reference when it is suitable :
computeNbDay(quotations, Quotation::isUpDay); computeNbDay(quotations, Quotation::isDownDay); computeNbDay(quotations, q -> q.isUpDay() && q.computeAbsolutePrct() >= 0.2F); |
All these ways of doing are both short and readable.
If the lambda expression is not too much complex and that this was not designed to be reused at multiple places, this way of doing should be favored.
However if the lambda expression is complex or that we need to reuse it in other cases, we should not inline the Predicate value.
For example, suppose that theses three predicates are often used by the client code, it would have more sense to extract them in some methods.
First, repeating a code multiple times is a bad smell that may cause many issues.
Second, we should unit test a predicate that is often used. By providing it with a method, we have a natural way to do it.
With an inlined lambda expression provided as an argument, we can only test the lambda during the test of the method where we passed the lambda. It may be annoying if it matters of test unitary the lambda or if we need to isolate it (mock/stub it) during some unit tests.
Here is an extraction of the 3 predicates :
public static Predicate<Quotation> isUpDayPredicate() { return Quotation::isUpDay; } public static Predicate<Quotation> isDownDayPredicate() { return Quotation::isDownDay; } public static Predicate<Quotation> isUpDayWithMinimalPrctPredicate(float minPrct) { return q -> q.isUpDay() && q.computeAbsolutePrct() >= minPrct; } |
We can now invoke the computeNbDay() by passing one of Predicate methods as argument :
computeNbDay(quotations, isUpDayPredicate()); computeNbDay(quotations, isDownDayPredicate()); computeNbDay(quotations, isUpDayWithMinimalPrctPredicate(0.2F)); |
In some cases, we will not extract the Predicate in a method as it is not designed to be reused but we consider that it should have an explicit name as its meaning may be not straight to read and that we don’t want to be forced to read each part of the predicate to understand it, each time we encounter the predicate in the reading of the code.
Suppose that we want to define a Predicate that is true if all theses conditions are true :
– the quotation is a down day.
– the absolute variation between the open and the close price is superior or equal to 20 %.
– the volume of the quotation is considered as significant.
While we could inline the Predicate in this way :
computeNbDay(quotations, q -> q.isDownDay() && q.computeAbsolutePrct() > 0.2F && q.getVolume()>SIGNIFICANT_VOLUME); |
It makes more clear to introduce a local variable with a meaningful name to store it :
Predicate<Quotation> isSignificantDownDay = q -> q.isDownDay() && q.computeAbsolutePrct() > 0.2F && q.getVolume()>SIGNIFICANT_VOLUME; computeNbDay(quotations, isSignificantDownDay); |
Thank you! Saved my f***ing day! :*