Coffee Breaks: Demystifying Lambdas
Cover page designed and developed by my Wife and Son

Coffee Breaks: Demystifying Lambdas

This is part 3 (of 3) of the excerpts from my book published on Amazon All About Java 8 Lambdas - A Weekend Read. Check out here for Part 1: Introduction to Java 8 Lambdas and Part 2: Java 8's New Features of the article series.

Java is an Object Oriented programming language. Methods and variables are tied to a class and the class must be instantiated before invoking the methods to execute the functions appropriately. We cannot have a variable or a block of code that does not belong to a class. 

Hence, a class forms a fundamental structure in Java. Even if it's a single line of business logic, it must be written in a method and declared in a class.

With the advent of Java 8, we can write blocks of code and pass them to other methods as if we are passing the data in the name of lamdas. A class used to be the first class citizen in Java, however, with the introduction of lambdas, the smallest unit of code is indeed a lambda!

This chapter is all about demystifying the lambdas and trying to understand the bigger picture.

What are Lambdas?

Lambdas are blocks of program code that represents behaviour, similar to a function. It can be passed around to other methods just as we pass data – hence we say code as data. They are anonymous functions and are concise, clean and expressive. They are sort of free floating code blocks helping us write functional code

Consider the following example:

// Lambda printing out a Quote each day
Quote quoteLambda = 
 ( ) -> System.out.println(“Hello, Lambdas!”);

// The functional interface definition:
interface Quote{
  void quoteOfTheDay();
}

This is a simple lambda expression which, when invoked prints out the quote-of-the-day to the console. Don't worry if it doesn't make any sense. All you need to know at this point is that it does something (printing out a message to the console) given no arguments.

Similarly, take another example of a lambda expression, say that extract the trade’s instrument:

// Lambda fetching the instrument of a Trade
Instrument<Trade> instrumentLambda =
  (t)-> t.getInstrument();

// Functional interface defintion:
interface Instrument<T>{
  String extract(T t);
}

In both examples, we have a functional interface and a lambda expression defined. The lambda expressions are new to us and have a special syntax and semantics. 

Lambdas are anonymous functions representing program behaviour. We can pass them just as we pass data to a method

Lambdas, in essence are mathematical functions, map their input to a unique output, including the case of an empty parameter list. They represent behaviour, behaviour such as to reserve a seat on a flight, convert the temperature from Centigrade to a Fahrenheit, merging two trades, or aggregating the entire wealth of high net worth individuals. We can use lambdas to parameterise this sort of functional behaviour.  Using lambdas we can model ever changing client requirements with ease and comfort.

Strictly speaking lambdas are not free floating code blocks but are functions implementing a special interface called functional interface. The functional interface must have one and only one abstract method defined.

Problem Domain

The best way to learn lambda feature is working through an example. 

Let's take some fictional requirements such as checking if the given trade needs encryption, or if it need to be offloaded due its associated market risk or to check if it can spilt up etc. Usually, we design and define an interface to capture and execute such behaviours. As we have already created an interface called Tradable with an abstract method check(T t) in our earlier chapters, we can use it as our base for understanding and dissecting the lambda’s signature and syntax. 

The definition of the interface is shown below once again for completeness:

// Functional interface for checking characteristics
interface Tradable<T>{
  boolean check(T t);
}

Now that we have a functional interface, we can develop few lambda expressions for variety of cases. Say, we wish to check if the trade is large, we create a lambda expression with appropriate business logic as demonstrated in the code snippet below:

Tradable<Trade> bigTradeLambda =(t) -> t.isBigTrade();

The right hand side of the assignment is a lambda expression. It says, given a Trade t, invoke isBigTrade method to return a boolean result (we will learn about the syntax real soon).

The type of a lambda expression is a functional interface and hence the above lambda is assigned to a variable of type Tradable<Trade>.

Here, we are creating an instance of Tradable with a lambda expression associated to it. 

However, instead of creating a lambda and assigning it to a type, we can instead pass the lambda expression directly to any method that accepts the Tradable<Trade> as parameter, like the checkCharecteristics method shown here in this snippet:

// Passing a lambda expression to the method
private void checkCharecteristics(Tradable<Trade> lambda, Trade t){
  tradableLambda.check(trade);
}

The method accepts a functional interface, thus abstracting the logic so clients can pass in several lambda expressions suited to their own requirements, as demonstrated here:

DissectingLambda client = new DissectingLambda();
Trade trade = new Trade(..);

//Is the trade in cancelled state?
client.checkCharecteristics 
  ((t) -> t.isCancelledTrade(), trade);

// Is it a risky trade
client.checkCharecteristics
  ((t)->  client.isRisky(), trade);

As you may have noted, the checkCharecteristics method is capable of receiving code expression that sticks to a specified signature: given a Trade, return a boolean value. In both the examples, we are declaring the lambda code inline and passing it to the checkCharecteristics method. Thus, we are delivering the required behaviour without having to duplicate code. Client has more flexibility to create such behaviours.

Now that we broadly may have understood the lambda, it is time to understand the special syntax that a lambda has.

Syntax

Consider the following lambda expression we had seen earlier:

( ) → System.out.println(“Hello, Lambdas!”);

The syntax may first seem a bit baffling. It is something we are not used to in Java language so far. There is a special (and new) syntax representing a lambda expression. But it is not too dissimilar to what a Java method is, except that its representation is a bit different. 

Lambda Constituents

The syntax of a lambda expression is derived from the functional interface definition. Similar to a normal method, lambda has input arguments and a body, a return value and possibly an exception. It is easy to grasp the syntax when we look at it from a method perspective. 

Lambda syntax is simple and follows the footsteps of a method. It has input parameters, body and return statement along with exceptions if any.

To keep things simple, we can split the lambda expression into two parts: left hand and right hand parts. The parts are separated using an arrow token (→) in between. The left part of this arrow is holder of input arguments to the lambda expression while the right side part is the body of the lambda. Body is where we apply business logic and return a result, void if nothing to return.

When creating a lambda expression, we take the input parameters on the left hand side and the body on the right hand side, separated by an arrow as demonstrated in the following code snippet:

(Input arguments) → { body }

Let’s compare this syntax with our earlier example to pick the right pieces from the expression:

( ) → System.out.println(“Hello, Lambdas!”);

The left hand side is where the arguments are presented, in this case the lambda has an empty parenthesis, meaning no input arguments are expected by this lambda. The right hand side is the body representing the business logic, printing a message in this case. The body is a simple print statement returning void and no exceptions declared. 

If we have only one parameter, the parenthesis is optional, the argument list becomes even simpler, as shown here below:

// Single parameter, see next snippet what we can do
(Trade t) -> t.getInstrument().equals("IBM")

//Drop the parenthesis and type
?t -> t.getInstrument().equals("IBM")

Deducing the Types

In the lambda expression t -> t.getInstrument().equals("IBM"), the input parameter is not declared with a type. Obviously, the Java compiler should ideally be unhappy by looking at this. However, we can ignore the type of the input parameters as they are inferred by the compiler from the functional interface’s definition. The compiler can deduce that t is indeed a Trade type.

Return Values

Declaration of a return value from the lambda’s body is interesting. The return type is explicit in some cases where we must declare the value output. Other cases it is implicit, in which case the return value is omitted. Look at the following lambda expression:

/ Lambda expression for a cancelled trade

// return is implicit
(t) -> t.isCancelledTrade()

We did not declare a return value in the body of the expression shown above because, as this is a simple expression, the output is implicitly returned to the caller. That is, we can ignore attaching a return keyword if the body is a statement or a simple expression.

However, if we have a bit of complex block of code block with various control paths, then we have to explicitly mark the return. 

Consider the following code segment, a copy of the same example as above.

// Return is implicit as the body is no more a simple statement/expression

t -> {
  t.fetch();
  return t.isCancelledTrade();
}

For multi-line code (each line is separated by a semi-colon delimiter) body, we need to use curly braces (shown in the above snipper) with an explicit return.

Look at an other example where lambda’s body is enclosed (simple logic) within braces where the return is mandatory:

// explicit return type required when braces are used
Greeting greetingLambda = s ->{
  return “Hello, ” +s; 
};

Lambda Type

Now that we have covered the syntax basics, next step is to find the mechanism of how a lambda is formed. The formation of a lambda expression, as mentioned earlier, depends on the definition of the functional interface.

Let’s consider a simple example of Hello, World! A functional interface (interface with one abstract method) for the greeting message is defined like this

// Functional interface
interface Greeting {
  String sayHello(String name);
}

This interface has a single abstract method sayHello, which accepts a name (String) and returns a message (String). As it has a single abstract method, we can define a lambda expression with Greeting as its type.

The sayHello method accepts an input of type String. Our lambda’s left part should resemble this. Hence the left hand side of lambda can be written as:

// Left part of the lambda
(String msg)

The right hand side of the lambda is the logic applied on the given input. The logic is to print out a message to the console. The code segment shown below shows this:

// Right part of the lambda
System.out.println("Hello, "+msg);

Now that we have the left and right part of the lambda, both parts are put together with an arrow operator operator separating them, thus producing a lambda expression:

// Final lambda expression
(String msg) -> System.out.println(“Hello, "+msg);

Let’s compare this with the concrete implementation of the Greeting interface. If were to implement this class, we will have a concrete implementation of the sayHello method will be somewhere along the lines as shown below:

Greeting oldGreeting = new Greeting(){
  @Override
  public void sayHello(String msg) {
    System.out.println("Hello, "+msg);			
  }
};

// This is the transformation to a lambda!
(String msg) -> System.out.println("Hello, "+msg);

Comparing the traditional implementation of Greeting interface with the lambda expression, you can see how the highlighted code from the traditional class implementation made it into a lambda! The main differences are the name of the method is foregone as well as the rest of the boiler plate code, leaving us with a simple expression.

Lastly, if we need to assign this expression to a variable, we should assign to our functional interface, from which we derived our lambda in the first place:

//Type is a functional interface
Greeting greetingLambda = 
 (String msg) -> System.out.println(“Hello, “+msg);

TradeMerger Lambda Example

To cement out understanding of formation of a lambda expression, let’s consider another example, this time with two inputs and returning an object. 

For this purpose, we create an interface called Merger which defines a contract for merging two trades producing a brand new trade. We define a single abstract method named merge accepting two trades:

// A functional interface for merging the trades

interface Merger{
  Trade merge(Trade t1, Trade t2);
}

As the method accepts two inputs, we write the input parameters in the parenthesis on the left hand side of the expression. On the right hand side we merge these two input trades to returning a merged trade (business logic is omitted for brevity). This is shown in the code segment below:

// Lambda expression 
(Trade t1, Trade t2) -> {
  Trade mergedTrade = new Trade(); 
  ....
  return mergedTrade;
}

// Even better, drop the type declarations
(t1, t2) -> { 
  Trade mergedTrade = new Trade(); 
  ....
  return mergedTrade; 
}

If you wish, you can drop the types of input arguments (as shown in the second part of the example snippet above) as they can be inferred by the compiler.

The type of a lambda is a Functional Interface. A functional interface is a normal interface with one feature: it must have a single abstract method if it expected to represent a lambda.

The last piece in the puzzle is the type of this lambda. The functional interface itself becomes the type of the lambda expression. Hence, the above lambda expression is assigned to a functional interface Merger as shown below:

// The lambda is assigned to a functional interface
Merger mergedTrade  = (t1, t2) -> {
  ....  
  return mergedTrade;
}

Summary

In this article, we laid the foundations of creating and forming a lambda expression. We learned that a lambda resembles a method signature, however syntax should adhere to a special format. We looked at lambda examples, along with creating custom functional interfaces and respective lambdas.

This is part 3 (of 3) of the excerpts from my book published on Amazon All About Java 8 Lambdas - A Weekend Read. Check out here for Part 1: Introduction to Java 8 Lambdas and Part 2: Java 8's New Features of the article series.






要查看或添加评论,请登录

Madhusudhan Konda的更多文章

社区洞察

其他会员也浏览了