Introduction to Lambda Expressions in Java


You will learn

  • Why do I need lambda expression in Java?
  • How do lambda expressions reduce the amount of code I write?
  • What is the purpose of Java lambda expressions?

We will discuss:

  • How using lambda expressions can reduce the amount of code we write
  • We can focus on the application logic (the “what”)
  • We don’t have to bother with the implementation logic (the “how”)

We assume you are aware of:

  • The meaning of terms object-oriented programming

Tools you will need

  • The JDK Environment installed
  • The JShell REPL tool
  • A code editor or an IDE, such as Eclipse

Programming Courses

The Object Oriented Way

Before we explain how FP is different from OOP, maybe we could see where exactly it fits in. Consider a typical program written to process a list. You need to create a List of String objects and iterate through it, printing out its contents.

A simple Java program that uses OOP concepts to do so, looks like this.

Example-01 : OOP List Traversal

FunctionalProgrammingRunner.java

import java.util.List;

public class FunctionalProgrammingRunner {

   public static void main(String[] args) {

   <String> list = List.of("Apple", "Banana", "Cat", "Dog");

      for(String str:list) {

         System.out.println(str);

      }

   }

}

Console Output

Apple

Banana

Cat

Dog

Snippet-01 Explained

  • The above example typifies the fact that the code revolves round the data. The logic of printing out the contents of list involves looping, where the programmer needs to both set up the mechanism and track the task progress. As you will see in the next step, FP changes this.

The Functional Programming Way

The concept of Functional Programming (FP) uses a different approach to manage state and behavior in your program. FP treats functions as first-class citizens, giving them the emphasis due to them. Object state within a program, does not get to steal the limelight any more. With FP, a function is a piece of code that does a unit of computation. It could be printing a list of strings on the console, computing the sum of the first 10 integers, and the like.

  • Imagine a program where instead of objects, functions are passed around to compute different stuff. Functions would then be treated on par with data. The curious few among you might ask:
    • Can you assign a function to a variable?
    • Can you pass a function as an argument to a method?
    • Can you obtain a function as a return value, from a method invocation?

We will see how Java FP answers these questions, and more. The next step introduces you to the basics of FP as implemented Java.

Defining Methods, The FP Way

The conventional approach (structural, and/or OOP) forces you to take care of both the what, and the how of a computation. We saw that not only did we need to access individual elements of a List during iteration, we also had to setup and track the progress of its iteration. The main activity, calling System.out.println() to print each element, was actually quite simple. A classic case of “Much ado about nothing!!”!

This is where FP comes across as a breath of fresh air. This approach allows you to focus mainly on the what, while it takes care of mechanisms to care for the how. To highlight the difference, it’s useful to compare and contrast. Let’s write both the OOP/Structural and the FP versions of such a logic, as separate methods. The following example should help you identify the shift in approach.

Example-02 : printBasic() And printFunctional()


//**_FunctionalProgrammingRunner.java_**

import java.util.List;

public class FunctionalProgrammingRunner {

	public static void main(String[] args) {

		List<String> list = List.of("Apple", "Banana", "Cat", "Dog");

		// printBasic(list);

		printFunctional(list);

	}

	public static void printBasic(List<String> list) {

		for (String str : list) {

			System.out.println(str);

		}

	}

	public static void printFunctional(List<String> list) {

		list.stream().forEach(

				element -> System.out.println(element)

		);

	}

}

Console Output

Apple

Banana_Cat

Dog

Example-02 Explained

  • printFunctional() showcases FP for you, the alert programmer! In plain english, one could describe this method as:
    • printFunctional() accepts list as an argument
    • It converts list into a Stream of String objects, and
    • forEach element in that Stream, do a console display using System.out.println().
  • The code element -> System.out.println(element) that appears as the argument to forEach(), is called a lambda expression. This is a directive, or a statement of action, that does actual computation in an FP setup. Roughly, it is called a function in FP terms.
  • Did you notice that in the code above, we passed a lambda expression as a parameter to a method (instead of an object or other data, as in the case of OOP)?

Looping Through A List

FP has opened a window to a whole new world for us. “Passing a function as a method argument”. Isn’t that out of this world? Now you’re curious to see what else this new approach can do. Maybe you want to rewrite every program you wrote so far, if you could.

Let’s do this one step at a time. Let’s also see if JShell can help us come to grips with FP. The following example tries to solve this problem: “Given a list of numbers, iterate through this list and print each number out.”

Example-03 : Loop Using FP

jshell> List list = List.of(1, 4, 7, 9);

list ==> [1, 4, 7, 9]

jshell> list.stream().forEach(elem -> System.out.println(elem));

1

4

7

9

jshell>

Example-03 Explained

  • Using FP, we hardly broke a sweat here! We could write the actual code in just one statement! The one thing that stands out here is the elegance of the code written. I would travel miles to write, and run that piece of code!

Filtering Results

If we asked you to write software to control a coffee machine, a lot of choices and options that users may have, would go into code as well. These choics would include coffee type, temperature, quantity, and the like. The code would need to filter out options based on a user’s choice, and not just filter the coffee!

Yes, data filtering is a major part of programming, and FP makes it simple for you with lambda expressions. The following example filters out certain strings from others, for instance.

Example-04 : Using filter()

package com.in28minutes.functionalprogramming;

import java.util.List;

public class FunctionalProgrammingRunner {

	public static void main(String[] args) {

		List<String> list = List.of("Apple", "Bat", "Cat", "Dog");

		// printBasicWithFiltering(list);

		printFPWithFiltering(list);

	}

	public static void printBasicWithFiltering(List<String> list) {

		for (String str : list) {

			if (str.endsWith("at")) {

				System.out.println(str);

			}

		}

	}

	public static void printFPWithFiltering(List<String> list) {

		list.stream()

				.filter(elem -> elem.endsWith("at"))

				.forEach(element -> System.out.println(element));

	}
}

Console Output

Bat

Cat

Example-04 Explained

  • A Stream object is nothing but a sequence of values from a collection. The filter() method is called on a Stream object, and it filters the Stream elements based on some logic. The logic is executed by the lambda expression passed to filter() as an argument.

Another example using filter

Why just strings, can’t filter() work with numbers as well? Can it be used to separate even numbers from the odd ones, from within a list? It sure can.

Example-05 : Printing even/odd numbers

jshell> List<Integer> list = List.of(1, 4, 7, 9);

list ==> [1, 4, 7, 9]

jshell> list.stream().forEach(elem -> System.out.println(elem));

1

4

7

9

jshell> list.stream().filter(num -> num%2 == 1).forEach(elem -> System.out.println(elem));

1

7

9

jshell> list.stream().filter(num -> num%2 == 0).forEach(elem -> System.out.println(elem));

4

jshell>

Example-05 Explained

  • The logic that runs the filtering part, actually encodes a condition in conventional programming. These conditions would be:
    • num is odd : if(num % 2 == 1) { /* */ }
    • num is even : if(num % 2 == 0){ /* */ }
  • In FP, we do this with lambda expressions:
    • num is odd: num -> num%2 == 1
    • num is even: num -> num%2 == 0

Notice the amount of code clutter that we have done away with. This is a very big reason why functional programming holds its own, when challenged by well-established programming practices.

Summary

In summary, we covered the following concepts in this article:

  • Functional programming focuses more on the “what”, rather than on the “how”
  • We reinforced this fact by writing a simple program to traverse a list, and compared it with the object-oriented approach
  • We also explored a built-in Java method called filter(), to extract desirable results from a list.

Next Steps

Complete Code Example

  • NA

Related Posts

Overloading Java Constructors - Be careful about it

Explains what choices are available to maximize reuse of constructor code

Java Printf - How to format console output

Format your Java Console output with printf

Introduction to Java Platform - JDK vs JRE vs JVM

Explains how The terms JDK, JRE and the JVM are related, but different

Executor framework in Java - Take control over your tasks

Understand how to use Executor from in Java with Code Examples

Operator Precedence - Understand How Java Evaluates Expressions

Explains Java Operator Precedence Rules. Helps you easily understand expression evaluation.

Java Exception Handling with Finally

Understand how adding a finally clause to a try catch block neatly releases resources if there is an exception

TODO

TODO