Tuesday, April 4, 2023

How Lambda Expression and Functional Interface work together in Java? Example

Hello guys, It's almost 9 years since Java 8 was released in March 2014 but Java programmers learning Java 8 still face problem understanding lambda expression and functional interface. Apart from syntax then two main question, which many Java developer ask to me is how to write lambda expression and where exactly we can use lambda expression? These two are really important question and you can answer them only if you truly understand lambda expression. I'll try to answer these questions in this article, which help you to understand both lambda expression and functional interface better. Well, let's first start with what is a lambda expression? I won't give you the bookish definition because that's only for purist not for a practical developer like me or you. For me lambda expression is a way to pass code just like data to a function or method

Until Java 8, it wasn't possible to pass a block of code to a method in Java unless it is wrapped inside a class because you can only pass an object to a method in Java

The Anonymous class was the only way to pass code to a function before. It was a trick where both class and object are combined together i.e. you define an anonymous class at compile time and create an object at runtime and that is passed to the method. 

This worked very well to implement Strategy pattern. For example, the Collectiosn.sort() method need a Comparator which is an Strategy to compare object and we can pass actual code in form of Anonymous class to define that strategy. 



What was the problem with Anonymous Class in Java?

The problem with the Anonymous class was that there were too much of boilerplate code which obscure the actual code, that problem is solved by lambda expression. Here, all boiler plate was removed and only the code which matter remains. 

For example, to sort a list of String by their length earlier we have to pass an Anonymous Comparator like this:

Collections.sort(list, new Comparator(){
  public int compare(String s1, String s2){
   return s1.length() - s2.length();
  }
});


You can do the same thing by using lambda expression like this:

Collections.sort(list, (s1, s2) -> s1.length() - s2.length());

You can see instead of passing a whole Anonymous class we just pass the code which maters i.e. 

s1.length() - s2.length()

This example is very important to understand how exactly lambda expression and functional interface work together. 

If you notice, here we are passing a lambda expression instead of Anonymous class, which means, you can lambda expression in place of Anonymous class but only in certain cases. 

As per rule, you can pass lambda expression to a method if it accepts a functional interface, now what is a functional interface? Nothing but an interface with just one abstract method or single abstract method e.g. Runnable, Comparable, Comparator, EventListener etc. 

That's why people says lambda in Java is of SAM type because you can only convert lambda expression to a functional interface in Java, other language also support functional literal but that's not supported in Java. The only use of lambda expression in Java is to convert it to functional interface. 

How Lambda Expression and Functional Interface work together in Java


Btw, apart from all existing interface with single abstract method e.g. Runnable, Callable, Comparable, there are many general purpose functional interface are added in JDK under java.util.function package e.g. Predicate, Supplier, Consumer, BiFunction etc. 

There is also an annotation called @functional which is used to mark an interface as functional but this is optional. What does that mean? It means, if you just put @Functional that will not make any interface functional, your interface must have just one abstract method to qualify as functional interface. 

Then what is the use of this @functional annotation? Well, it serves two purpose, first it checks if your interface actually have just one abstract method i.e. it ensures its functional and it also generate Javadoc to indicate that your interface is functional.

5 Essential Functional Interfaces Java developer should Learn

Coming back to some of the popular functional interface from java.util.fucntion package, that will help us to answer your second question e.g. how to write a lambda expression in order to pass to a method. 

Predicate<T> 
This interface accepts one argument of type T and return a boolean, which means you can use it to specify condition which results in boolean e.g. price > 1000 etc. 

Consumer<T>
This functional interface accept one argument and return nothing i.e. void

Function<T, R>
This functional interface accepts one argument of type T and return a value of type R. 

Suppoer<T>
This functional interface doesn't accept any argument but return a value of type T e.g. a factory method. 

Now, suppose, you have a method which accept a Predicate then you can you pass a lambda expression to it which results in true or false. For example, the filter() method of Stream accepts a Predicate:

list.filter( s -> s.length() >10)

if you look closely the lambda expression is :

s -> s.length() > 10

where s is argument and s.length > 10 is actual code. This is equal to a method like :

public boolean test(String s){
    return s.length > 10;
}

So, we are passing a function or code to method, that's why lambda expression is also known as anonymous function. 

In short, if a method accept a functional interface, you can pass a lambda expression to it. This answers our first question. 

The beauty of lambda expression is that it reduces boiler plate code that's why you don't need to write the code to create method, you just wrote the actual logic. Java also infer type and that's why we didn't specify whether s is String, Java automatically knew it from the context, but if you are starting with, you can also do:

String s -> s.length > 10

later, once you gain experience you will write only code which matter and leave everything to the javac. 

Another important thing about writing lambda expression is that you need to write it based upon what is expected. For example, if a method is expecting a Comparator which means it is expecting a function with two argument which return an int, remember the syntax of compare() method?

So, you can write like this:

(s1, s2) -> s1.length() - s2.length()

because we had two arguments we uses parenthesis in the left hand side i.e. (s1, s2), you can omit that if you have just one argument. Similarly on the right hand side of -> (arrow operator) we write code which result in integer, you don't need to specify return statement if your code is just one liner, lambda does it for you. 

Since, you know that s1 and s2 are String you can call the length() method of them, without specifying type, Java will infer the type automatically. So, where you need to compare String by their length, you can pass following lambda expression e.g. 

Collections.sort(list, (s1, s2) -> s1.length() - s2.length());

You can see, it's much cleaner and succinct. 

Now, let's take another example of lambda expression, this time little bit complex. If you have read my article about merging map in Java then you know that there is a merge() method added into java.util.Map interface in JDK. 

This method gives you the flexibility to decide values if keys from both map classes i.e when you have duplicate keys. This is done by passing a remapping function which is a functional interface called BiFunction<T,U,R>

This functional interface takes two arguments T and U and return an object of R type. Now, if you have two Map of integer and String e.g. Map<Integer, String> and you want to merge them, you can write code like this:

for(Entry<Integer, String> e : otherMap.entries()){
   map.merge(e.getKey(), e.getValue(), (v1, v2) -> (v1 + ":" + v1));
}

What is happening here? well, in case of same key, the remapping function is called which is :

(v1, v2) -> (v1 + ":" + v1)

If you look closely you will find that lambda has two argument and it return an object, which means you can convert this to a Bifunctional functional interface, which actually means you can pass this to merge() method. 

The two argument v1, and v2 are of type String but you didn't need to specify that and they are value from first and second map, all you did is to concatenate them (v1 + ":" + v1).

In short, how you write a lambda expression depends upon which functional interface you want to convert it and that depends upon the method you are passing the lambda expression. If method accepts a Comparable which means you can pass a lambda which accepts two argument and return an int. 

If it's Event Listener then you can pass a lambda which has one argument and return nothing e.g. it just print an statement or do nothing. If it's a BiFunction then you can pass a lambda which accepts two argument and return a third object. 

That's all about how lambda expression and functional interface work together. As a Java developer you must understand these basics e.g. any interface in Java with a single abstract method is functional interface and you can pass a lambda expression to it. You should also remember how to write lambdas, it depends upon which functional interface you want to convert it. 

Remember, you can only convert lambdas into functional interface in Java, you can't even assign a lambda to Object, because it's not an object. If you want o learn more about lambda expression in Java 8, I suggest you to go through Java 8 Master Class, one of the best course to learn lambdas and other features of Java 8. 

No comments :

Post a Comment