Tuesday, December 24, 2013

Introduction to Java 8 lambda expressions

Value parameterization is useful - but only to certain extent. When we try to handle additional use cases, we find the need to handle many special cases. Some developers, try to deal with these special cases using special values (-1, Integer.MAX_VALUE, null) However this is error prone and adds unnecessary complexity to the code. 

Lets start by looking at an example. Say we are developing a car sales application.
The entity class may look something like this.


 public class Car {

   private String make;
   private String model;
   private String type;
   private Integer year;
   private Integer kilometers;
   private String colour;
   private Transmission transmision;
   private BigDecimal price;

   //getters and setters
 }


A functionality required for the app might be the ability to display between a year range. The following method would cater this;


 void showCarsFilterByYearRange(Integer min, Integer max){
   for (Car c : getAllCars()) {
    if(c.getYear() > min && c.getYear() < max){
     display(c);
    }
   }
 }



We can call this with a range like


 showCarsFilterByYearRange(2000, 2005);


This works fine for the range, but we are forced to provide a max year even if we don’t want to. Maybe we can modify the method to support null value params and treat it as a special value ( in this case, when max is null we can safely substitute Integer.MAX_VALUE in it’s place as we are dealing with years here )

 void showCarsFilterByYearRange(Integer min, Integer max){
   for (Car c : getAllCars()) {
    if(c.getYear() > ((min != null)? min : 0)
      && c.getYear() < ((max != null)? max : Integer.MAX_VALUE)){
     display(c);
    }
   }
  }


Now we can only display cars after a particular YOM by passing in null for max


 showCarsFilterByYearRange(2000, null);


Sure this works, but what about additional search requirements? We are bound to need to search vehicles by other parameters such as price, kilometers, transmission. And surely, you should also be able to apply multiple filters? Our approach is obviously quite brittle and code complexity could increase exponentially with each new requirement.

The solution? Parameterization of behaviour (as opposed to values and types)  
The expected behavior should be able to be passed as a function...

In our usecase we need to pass the car filtering logic as such a function. Unfortunately, (at least before Java 8) you cannot just pass a method as a parameter - you can only pass instances of objects. Therefore, this will need to be implemented using functional interfaces (also known as Single Abstract Method (SAM) interfaces).

A functional interface is an interface with only one method. In our case, this method needs to apply a filter to a Car and return a boolean flag.
 public interface CarPredicate {
   boolean test(Car p);
 }


We can have a single filter method that will expect an instance that implements this interface, and uses test method to check the cars.
 public void showFilteredCars(CarPredicate pred){
   for(Car c: getAllCars()){
    if(pred.test(c)){
     display(c);
    }
   }
 }


On the caller end, we can wrap an anonymous inner class declaration and instantiation along with an implementation for the test method and pass it to our showFilteredCars().  In the example below we are displaying the cars made after 2008.
 showFilteredCars(new CarPredicate(){
   public boolean test(Car c){
    return c.getYear() > 2008;
   }
 });

Note we’ve now parameterized the behaviour! The filtering logic is pushed out to the caller, which means we can do any type of filtering without having to touch showFilteredCars(). For example if we need to filter and display cars made after 2008 with manual transmission, the caller just needs to add that logic in;
 showFilteredCars(new CarPredicate(){
   public boolean test(Car c){
    return c.getYear() > 2008 
     && c.getTransmision().equals(Transmission.MANUAL);
   }
 });


It’s instantly obvious that this is a much better implementation. However, there is just too much boilerplate code here which deters programmers from following this approach. That’s where Java8 Lambda expressions come in - we can define the same logic as above with the minimum effort, only using the ‘important bits’.
Revisiting the previous example below, I have highlighted what we can consider the ‘important bits’. I.e (1) the parameter the predicate takes, and (2) the logic of what it returns.
 showFilteredCars(new CarPredicate(){
    public boolean test(Car c){
     return c.getYear() > 2008;
    }
 });

With Java 8 lambda expressions (also called closures) we only need to specify these two things using the following syntax.
 showFilteredCars(c -> c.getYear() > 2008);


It’s important to note that internally still gets converted to an instance of a functional interface. The compiler figures out what type c has to be through type inference.

Note that we did not need to update our showFilteredCars() implementation. However, it turns out that a functional interface that takes an object and returns a Boolean is such a common case that Java8 also provides and generalized predicate so that we don’t have to write our own.
 Interface Predicate {
   Boolean test(T t);
 }

So for completeness, we may update our showFilteredCars() to use this instead.
 public void showFilteredCars(Predicate pred){
   for(Car c: getAllCars()){
    if(pred.test(c)){
     display(c);
    }
   }
 }



The sample code for the above examples are available here

Eclipse IDE with support for Lambda expressions can be found here