Java 8 introduced the concept of Lambda expressions which enables you to implement functional-style programming in Java. Functional programming allows you to focus on ‘what to do’, rather than ‘how to do it’. It allows you to pass in code blocks as a method argument, instead of simply passing the data.
This can be very interesting and of high use for us as programmers. To understand it , consider a situation when you are writing a code of filtering out some contents from a given data structure(say an ArrayList) based on different conditions. How will you go ahead implement it using Java 7 or other lower Java versions ?
If you are a newbie, you’ll implement something like :
import java.util.*; public class MainClass{ public static void main(String []args){ User user1 = new User("Aaron",26); User user2 = new User("Sandy",24); User user3 = new User("James",30); List<User> userList = new ArrayList<>(); userList.add(user1); userList.add(user2); userList.add(user3); UserFilter filter = new UserFilter(); for(User user : userList){ if(filter.testByAge(user)){ System.out.println(user.name +" is eligible by age!"); } } System.out.println("--------------------------------"); for(User user : userList){ if(filter.testByName(user)){ System.out.println(user.name +" is eligible by name!"); } } } } class User{ public String name; public int age; public User(String name,int age){ this.name=name; this.age=age; } } class UserFilter { public boolean testByAge(User user){ return user.age>=25; } public boolean testByName(User user){ return user.name.startsWith("A"); } }
If you are an experienced Java Programmer with the knowledge of interfaces, you’ll refine your code like this:
import java.util.*; public class MainCLass{ public static void main(String []args){ User user1 = new User("Aaron",26); User user2 = new User("Sandy",24); User user3 = new User("James",30); List<User> userList = new ArrayList<>(); userList.add(user1); userList.add(user2); userList.add(user3); UserFilterByAge filter1 = new UserFilterByAge(); for(User user : userList){ if(filter1.test(user)){ System.out.println(user.name +" is eligible by age!"); } } System.out.println("--------------------------------"); UserFilterByName filter2 = new UserFilterByName(); for(User user : userList){ if(filter2.test(user)){ System.out.println(user.name +" is eligible by name!"); } } } } class User{ public String name; public int age; public User(String name,int age){ this.name=name; this.age=age; } } interface Filter{ boolean test(User user); } class UserFilterByAge implements Filter { public boolean test(User user){ return user.age>=25; } } class UserFilterByName implements Filter { public boolean test(User user){ return user.name.startsWith("A"); } }
But still , if you see even in the above approach, their is duplication of code in terms of the fact that you’ll have to implement new classes each time you go ahead writing a new type of filter. All time favourite question of all programmers – Can we do better? Till Java 7, No.
Yes for Java 8, using Lambda expressions. We’ll look at it in a while.
Functional Interface is nothing more than any Java interface which has exactly one non-Object class abstract method. It can have any number of default and static methods.
//invalid - equals belongs to Object class interface FunctionInterfaceSample1{ boolean equals(Object obj); } //valid - equals belongs to Object class, getName() is static interface FunctionInterfaceSample2{ boolean equals(Object obj); int getMarks(); static String getName(){return "Aaaron";} }
So, if you now understand the definition of Functional Interfaces, you’ll guess it right that Runnable
, Comparable
etc are also functional interfaces. Apart from the pre-existing functional interfaces, there are many new functional interfaces defined in Java 8 in java.util.function
package like Predicate<T>
, Function<T,R>
, Supplier<T>
, Consumer<T>
etc. You can learn about each one of them at the Oracle website here.
Lambda expressions implement functional interfaces by defining anonymous functions which can be passed as a method argument or used as values in a program.
//Creating thread prior to Java 8 Runnable r1 = new Runnable() { public void run() { System.out.println("I run method"); } }; Thread t1 = new Thread(r1); t1.start(); //Creating a thread in Java 8 // () -> {System.out.println("In run method")}; is a lambda expression Runnable r2 = () -> {System.out.println("In run method")}; Thread t2 = new Thread(r2); t2.start();
Now, let’s move back to the original problem of filtering out the contents and let’s try to implement the same logic using the concept of Functional Interface and Lambda expressions, it simplifies to the one given below:
import java.util.*; import java.util.function.*; public class MainCLass{ public static void main(String []args){ User user1 = new User("Aaron",26); User user2 = new User("Sandy",24); User user3 = new User("James",30); List<User> userList = new ArrayList<>(); userList.add(user1); userList.add(user2); userList.add(user3); Predicate<User> predicateForAge = (u) -> u.age >= 25; Predicate<User> predicateForName = (u) -> u.name.startsWith("A"); for(User user : userList){ if(predicateForAge.test(user)){ System.out.println(user.name +" is eligible by age!"); } } System.out.println("--------------------------------"); for(User user : userList){ if(predicateForName.test(user)){ System.out.println(user.name +" is eligible by name!"); } } } } class User{ public String name; public int age; public User(String name,int age){ this.name=name; this.age=age; } }
In the above code, we have used a predefined Functional Interface – Predicate<T>
of package java.util.function
which has the following definition:
public interface Predicate<T>{ /* Evaluates this predicate on the given argument * @param t the input argument * @return {@code true} if the input argument matches the predicate * otherwise {@code false} */ boolean test(T t); }
The code that will be executed when test()
method is invoked is determined by the lambda expression to which this Functional Interface refers at any given time.
The code has become so clean and elegant. Also, if we intend to add more such filters , it is a matter of just adding a few more predicates(one-liners) and we are sorted.
Isn’t it beautiful?
We have seen only one scenario of why use Lambda Expressions. There are a lot of predefined functional interfaces in java.util.function
package like Consumer<T>
, Supplier<T>
, Function<T,R>
, Predicate<T>
etc that serve different purposes when used in our code.
Also, the collection framework has exposed many new methods to support and encourage the use of lambda expressions. Some simple example :
List<Integer> myArrayList = new ArrayList<>(); myArrayList.add(1); myArrayList.add(2); //Printing an arraylist of integers using Lambda Expression myArrayList.forEach( val -> System.out.println(val)); //Printing ArrayList in Java 7 or lower versions for(Integer i : myArrayList){ System.out.println(i); }
To conclude, the use of Lambda expressions makes our life a lot easier as a developer and beautifies our code, reducing LoC(Lines of code) significantly.