Lazy Evaluation in Functional Programming

Welcome to Lazy Evaluation in Functional Programming

Welcome to another exciting lesson in our advanced course on Functional Programming with Java! Previously, we explored dynamic type handling with generics. Today, we’ll dive into Lazy Evaluation—a technique that can help you write more efficient and modular code by deferring computation until it's actually needed. Let's get started!

What You Will Learn

In this lesson, we will cover:

  • Understanding lazy evaluation.
  • Implementing lazy evaluation using the Supplier interface in Java.
  • Understanding how streams support lazy evaluation.
  • Practical use cases for lazy evaluation.

By the end of this lesson, you’ll know how to implement lazy evaluation in Java, enabling your programs to become more efficient and modular by computing values only when necessary.

Understanding Lazy Evaluation

Lazy evaluation is a powerful technique where the computation of a value is deferred until the moment it is actually needed. Instead of computing everything upfront, you delay the execution of the computation until its result is required, which can significantly improve the efficiency and modularity of your programs.

Lazy Evaluation with Streams

Java's Stream API is a prime example of lazy evaluation in action. When you work with streams, most operations (such as filtering, mapping, and sorting) are intermediate operations that are lazily executed. This means they don't perform any computation until a terminal operation (like collect, forEach, or reduce) is invoked.

Here’s an example to illustrate this:

import java.util.Arrays;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

        // Creating a stream and applying intermediate operations
        names.stream()
             .filter(name -> {
                 System.out.println("Filtering: " + name);
                 return name.startsWith("C");
             })
             .map(name -> {
                 System.out.println("Mapping: " + name);
                 return name.toUpperCase();
             })
             .forEach(name -> System.out.println("Final output: " + name));
    }
}

In this example:

  • Stream Creation: We start with a list of names and create a stream from it.
  • Intermediate Operations: The stream applies two intermediate operations—filter and map. The filter operation filters out names that don't start with "C", and the map operation converts the remaining names to uppercase.
  • Terminal Operation: The forEach operation is the terminal operation that triggers the execution of the stream.

Key Point: The intermediate operations (filter and map) are lazily executed, meaning they are not actually run until the terminal operation (forEach) is invoked. This lazy execution ensures that operations are only performed when absolutely necessary, optimizing the performance and efficiency of the program.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal