ZOFTINO.COM android and web dev tutorials

Java Lambda Expressions

Lambda expressions allow you to create functions and pass them as arguments to methods. Lambda expressions are introduced in Java 8. Lambda expression represents a functionality which is usually added to a method of one method interface.

Lambda expressions reduces the code required to pass functionality to a method as it eliminates the need to implement an interface, instantiate an object of it and pass it to the method.

Table of Contents

Introduction to Lambda Expressions

In Java, if a method needs to execute certain functionality, a method with the functionality can be defined within the class so that the method which depends on the functionality will call the method. The problem with this approach is it will be difficult to make changes to the functionality due to coupling.

For example, setMessage() method in the Message class calls displayMessage() methods.

public class Message {
	private String message;

	public void setMessage(String msg) {
		message = msg;
		displayMessage(msg);
	}
	
	public void displayMessage(String msg) {
		System.out.println("msg "+ msg);
	} 
}
	Message m = new Message();
		m.setMessage("Flash sale upto 20% off");

To solve the problem, the functionality can be created as a method in a separate class and pass an instance of the class to the method of another class which will execute it on demand. The problem with this approach is an extra class is created for the functionality which will be used only inside a class.

Now display displayMessage() method is moved to a different class.

public class MessageListenerImpl implements MessageListener{
	public void displayMessage(String msg) {
		System.out.println("msg "+ msg);
	}
}
public interface MessageListener {
	public void displayMessage(String msg);
}

Now, the Message class will look like this.

public class Message {
	private String message;

	public String getMessage() {
		return message;
	}

	public void setMessage(String msg, MessageListener ml) {
		message = msg;
		ml.displayMessage(msg);
	}
}

Here is how on demand functionality is passed to setMessage() method.

public class MainClass {

	public static void main(String[] args) {
		MainClass mainClass = new MainClass();
		mainClass.sendMessage();
	}
	public void sendMessage() {		
		Message m = new Message();
		m.setMessage("Flash sale upto 20% off", new MessageListenerImpl());
	}
}

Instead of separate class, you can define an inner class and add the functionality to the inner class to make it concise. To implement this in our example, we need to just move the MessageListener to inside of MainClass class.

public class MainClass {

	public static void main(String[] args) {
		MainClass mainClass = new MainClass();
		mainClass.sendMessage();
	}
	public void sendMessage() {		
		Message m = new Message();
		m.setMessage("Flash sale upto 20% off", new MessageListenerImpl());
	}
	class MessageListenerImpl implements MessageListener{
		public void displayMessage(String msg) {
			System.out.println("msg "+ msg);
		}
	}
}

We can still make it more concise by declaring MessageListener as a local class. Local class makes sense because it can access local variables and it is used within the method.

public class MainClass {

	public static void main(String[] args) {		
		MainClass mainClass = new MainClass();
		mainClass.sendMessage();
	}
	
	public void sendMessage() {
		class MessageListenerImpl implements MessageListener{
			public void displayMessage(String msg) {
				System.out.println("msg "+ msg);
			}
		}		
		Message m = new Message();
		m.setMessage("Flash sale upto 20% off", new MessageListenerImpl());
	}
}

Since local class is used only once, we can make it anonymous class to improve it further.

public class MainClass {

	public static void main(String[] args) {		
		MainClass mainClass = new MainClass();
		mainClass.sendMessage();
	}
	
	public void sendMessage() {
		Message m = new Message();
		m.setMessage("Flash sale upto 20% off", 
				 new MessageListener(){
						public void displayMessage(String msg) {
							System.out.println("msg "+ msg);
						}});
	}
}

Since the interface has just one method, one method interface is called functional interface, implementation of the interface can be defined without the method name. To do that, we need to use lambda expressions.

Lambda expressions simplify the declaration of anonymous classes by omitting new operator, interface name, parenthesis and the method name as shown below. Only arguments and body in the form of lambda expression is passed to the method call.

public void sendMessage() {
		Message m = new Message();
		m.setMessage("Flash sale upto 20% off", 
				(String msg) -> System.out.println("msg "+ msg));
	}

Here lambda expression is:

(String msg) -> System.out.println("msg "+ msg));

Functional Interfaces

In the above lambda expression example, the declaration of method setMessage() to which the lambda expression is passed contains MessageListener interface as an argument.

Interface MessageListener is a functional interface as it contains just a method. This type of interfaces is very common in applications. That is why JDK provided common functional interfaces which are defined in java.util.function and can be used in your application instead of you defining them redundantly.

In the example, displayMessage()method of MessageListener takes String as argument and returns no result. The Consumer functional interface from java.util.function can be used in our example instead of MessageListener interface as Consumer is a generic interface which takes one argument and returns no result.

public class Message {
	private String message;

	public String getMessage() {
		return message;
	}

	public void setMessage(String msg, Consumer<String> ml) {
		message = msg;
		ml.accept(msg);
	}
}

Some of the widely used common functional interfaces provided by JDK are :

  • BiConsumer : Has accept() method which takes two arguments and returns no result.
  • Predicate : Contains test() method which takes one argument and returns boolean.
  • Function : Contains apply() method which takes one argument T and returns R.
  • BiFunction : Contain apply() method which takes two arguments and returns result R.
  • Supplier : Contains get method which returns result T.

Lambda Expression

Now you know the context in which using lambda expression makes sense and helps in reducing the complexity and the amount of required code. Let’s understand how to write the lambda expressions.

Lambda expression starts with a comma separated list of parameters in parenthesis.

(Integer count)

Then arrow token.

->

Then body containing single expression or statement block.

System.out.println("total "+ count)

Here is the complete lambda expression:

(Integer count) -> System.out.println("total "+ count));

No Parameter Lambda Expression

To create no parameter lambda expression, start the expression with empty parenthesis.

Functional interface Supplier provided by JDK has get method which doesn’t take any arguments but returns results. Below example uses Supplier interface to show no argument lambda expression. In the example, no argument lambda expression is passed to the method getRandomValue() of NumberUtil class.

	NumberUtil nu = new NumberUtil();
		int randVal = nu.getRandomValue( 
				() -> {
					Random random = new Random();
					return random.nextInt(400) + 20;
				});
		
		System.out.println("returned from lambda "+ randVal);
public class NumberUtil {
	public int getRandomValue(Supplier<Integer> supplier) {
		return supplier.get();	
	}
}

Single Parameter Lambda Expression

Below example shows how to use single parameter lambda expression which is passed to isDataValid() method in Validator class. The method takes Predicate as parameter. The lambda expression is matched to Predicate.

	Validator v = new Validator();
		String city = "Bellevue";
		boolean isValid = v.isDataValid( city,
				(String info) -> {
					String regx = "^[a-zA-Z0-9]*$";
					return info.matches(regx);
				});
		
		System.out.println("returned from lambda "+ isValid);
public boolean isDataValid(String data, Predicate<String> predicate) {
		return predicate.test(data);	
	}

Multiple Parameters Lambda Expression

Below example shows two parameters lambda expression which is matched to BiConsumer.

	Message m = new Message();
		
		m.printStringInteger("Java" , 9,
				(String str, Integer numb) -> {
					System.out.println("values "+ str+" "+numb);
				});
	public void printStringInteger(String str, Integer numb, 
			BiConsumer<String, Integer> biConsumber) {
		biConsumber.accept(str, numb);
	}

Lambda Expression Body with Single Statement

If lambda expression body has single statement, braces can be omitted.

(String msg) -> System.out.println("msg "+ msg));

Lambda Expression Body with Multiple Statements

If lambda expression body has multiple statements, statement needs to be defined in block.

(String info) -> {
		String regx = "^[a-zA-Z0-9]*$";
		return info.matches(regx);
	});

Returning Result from Lambda Expressions

If lambda expression contains single expression, then the lambda will return the value that the expression evaluates to.

In the below example, lambda contains single expression and the calculated value will be returned. Notice that the method calculateDiscount() takes Function as parameter and the apply() method of Function takes the Integer as parameter and returns the Integer.

	Message m = new Message();
		int price = m.calculateDiscount(22, 
				(Integer price) -> price * 10/100);
public class Pricer{	
	public Integer calculateDiscount(int val, Function<Integer, Integer> function) {
		int a = function.apply(val);
		retun a+100;
	}	
}

If lambda expression contains statement block and lambda expression needs to return a result, return statement needs to be used.

For example below lambda expression takes time in milliseconds as input and converts it to into string format and returns the value.

DateUtil du = new DateUtil();
String dt = du.getDateInRequiredFormat(Calendar.getInstance().getTimeInMillis(), 
		(Long time) -> {
			DateFormat dFormat = new SimpleDateFormat("dd/MMM/yyyy");
			return dFormat.format(time);
		});

System.out.println("returned from lambda "+ dt);
public class DateUtil {
public String getDateInRequiredFormat(long timeInMillis, 
		Function<Long, String> function) {
return function.apply(timeInMillis);		
}

}

Using Generic Multiple Lambda Expressions

In the example below, getFinalOrderAmt() method in OrderUtil class takes two lambda expressions which match to Supplier and BiFunction types. The parameter and return types of Supplier and BiFunction are declared as generic. Result from one lambda expression becomes argument of another lambda expression.

	OrderUtil ou = new OrderUtil();
		
		Double orderAmt = 950.50;
		Double finalAmt = ou.getFinalOrderAmt(orderAmt,
				() -> {
					return "CPN50";
				},
				(Double amt, String coupon) -> {
					if("CPN50".equals(coupon)) {
						return amt*50/100;
					}else {
						return amt*80/100;
					} 
				});
		System.out.println("final amt "+ finalAmt);
	public <T, S> T getFinalOrderAmt(T orderAmt, Supplier<S> supplier,
			BiFunction<T, S, T> function) {
		S coupon = supplier.get();
		T orderAmtAfterDisc =  function.apply(orderAmt, coupon);
		return orderAmtAfterDisc;
	}

Lambda Expressions with Aggregate Operations

Lambda expressions can be used with collection’s aggregate functions such as map, forEach, allMatch, findAny, filter, filterMapp, .etc, as shown below.

	boolean valid = coupons.stream().allMatch(
				(String cpn) -> {
			String regx = "^[0-9]*$";
			return cpn.matches(regx);
		});
		
		System.out.println("coupons are valid "+ valid);

Lambda expressions are used in RxJava, see RxJava operators for more information.

Accessing Local and Member Variables

Similar to local and anonymous classes, lambda expressions can access local and member variables.

Method References

So far, we have seen using lambda expressions to define anonymous methods. In cases where lambda expression body contains just a call to an existing method, method reference can be used to concise the code and make it easy to read.

Below lambda expression validates list of items and displays validation status.

validator.validateShowMessage(list,
		(List<String> itemList ) -> {			
			for (String item : list){
				String regx = "^[a-zA-Z0-9]*$";
				if( item.matches(regx)) {
					System.out.println("valid item "+item);
				}else {
					System.out.println("invalid item "+item);
				}
			}		
			
		});

Here is validateShowMessage() method in Validator class.

public void validateShowMessage(List<String> dList, Consumer<List<String>> consumer) {
	consumer.accept(dList);
}

If the validation logic is already defined in an existing class, you can simply call that method in the lambda expression.

validator.validateShowMessage(list,
		(List<String> itemList ) -> {						
			ValidatorUtils.validateData(itemList);
		});

Lambda expression just calls existing method, this type of lambda expressions can be replaced with method reference as shown below.

validator.validateShowMessage(list, ValidatorUtils::validateData);

Here since validateData() method is a static method, method reference uses class name, then two colons and method name. Java runtime infers parameters to the method.

Method Reference Types

Method reference can be a reference to a static method, member method, method of an arbitrary object of a type, or constructor.

Reference to static method

Below example shows method reference to a static method.

validator.validateShowMessage(list, ValidatorUtils::validateData);

Reference to member method

Here is an example of method reference to a member method.


ValidatorUtils vUtils = new ValidatorUtils();
		
vUtils.validateShowMessage(list, vUtils::validateData);

Reference to method of an arbitrary object of a particular type

If a lambda expression is called repeatedly for the same type of object and the expression body just contains a call to one of the methods of the object, then you can use method reference similar to how it is used to refer to static methods.

public class Coupon {
	private int discount;
	public int getDiscount() {
		return discount;
	}
	public void setDiscount(int discount) {
		this.discount = discount;
	}
	public void isBigDiscount() {	
		if(this.getDiscount() > 70) {
			System.out.println("big discount");
		}else {
			System.out.println("not a top offer");
		}
	}
}
List<Coupon> coupons = new ArrayList<Coupon>();
Coupon c = new Coupon();
c.setDiscount(20);
coupons.add(c);
c = new Coupon();
c.setDiscount(30);
coupons.add(c);

c = new Coupon();
c.setDiscount(80);
coupons.add(c);

//lambda expression
coupons.forEach((cpn) -> cpn.isBigDiscount());

//arbitrary method reference
coupons.forEach(Coupon::isBigDiscount);

Reference to constructor

Here is an example of reference to constructor. Below method returns Coupon object.

public Coupon getCoupon(Supplier<Coupon> supplier) {
	return supplier.get();	
}

Here is how the above method is called using lambda expressions.

Coupon cpn = m.getCoupon( 
		() -> {
			return new Coupon();
		});

Here is how the above method is called using constructor reference.

Coupon cpn = m.getCoupon(Coupon::new);