A Little Lazy Lambda Tutorial

javajava 8

Everyone is a bit lazy, so why not our code? Lambda expressions in Java 8 allow us to make our code that bit more lazy. In this short post I will cover how using Lambda expressions can make code more lazy.

If you have not used Lambda expressions before have a look at my some of my previous posts A Little Lambda Tutorial and Java 8 Streams for some background information not in this post.

So first things first, what does being lazy mean? We’ll I’m sure you know the answer as you have probably left things to the last minute and then quickly rushed to complete it at the end or even not do it at all. That is basically the definition of Lazy Evaluation, which defers the evaluation of a method until another expression requires it. If an expression never calls the lazy method then it will just lie around and do nothing. Java on the other hand uses Eager Evaluation by default and will execute a method as soon as it is called.

Lambdas allows Java to be lazy as they represent a function to be executed that can be passed around and will only be evaluated when required. The examples below are pretty simple but should demonstrate the order of execution showing that the Lambdas are not executed straight away.

If we start with a basic piece of code that is not using Lambdas we can see how they execute sequentially.

public class NonLazyCodeExample {
  public static void main(String args[]) {

    final int number = 4;
    final boolean computeResult = compute(number);
    final boolean processResult = process(number);

    if (computeResult && processResult) {
      System.out.println("TRUE");
    } else {
      System.out.println("FALSE");
    }
  }

  public static boolean compute(final int number) {
    System.out.println("computing number : " + number);
    return number > 5;
  }

  public static boolean process(final int number) {
    System.out.println("processing number : " + number);
    return number % 3 == 0;
  }
}

Which outputs

computing number : 4
processing number : 4
FALSE

This is due to both compute and process being called straight away when they are assigned to their variables. The execution of process was wasted as the second condition of the if statement was never reached. Obviously this example is extremely simple and most people will mention that the code can be rewritten to

public class NonLazyCodeExample {
  public static void main(String args[]) {

    final int number = 4;

    if (compute(number) && process(number)) {
      System.out.println("TRUE");
    } else {
      System.out.println("FALSE");
    }
  }

  public static boolean compute(final int number) {
    System.out.println("computing number : " + number);
    return number > 5;
  }

  public static boolean process(final int number) {
    System.out.println("processing number : " + number);
    return number % 3 == 0;
  }
}

Which will now output

computing number : 4
FALSE

As the process method is never called due to the if statement evaluating to false as soon as compute is executed. So why not do this in the first place? A possible reason against this is if longer methods are put into the if statement, maybe we a few parameters, it will start to get messy and you might struggle to read it. Whereas executing the methods earlier and assigning them to variables should make the if statement itself shorter and easier to understand.

Using Lambdas will combine both readability and removal of unneeded computations. By allowing us to define them to a variable without executing them. The example below should make this clearer.

public class LazyCodeExample {
  public static void main(String args[]) {

    final int number = 4;
    final Supplier<Boolean> computeResult = () -> compute(number);
    final Supplier<Boolean> processResult = () -> process(number);

    if(computeResult.get() && processResult.get()) {
      System.out.println("TRUE");
    } else {
      System.out.println("FALSE");
    }
  }

  public static boolean compute(final int number) {
    System.out.println("computing number : " + number);
    return number > 5;
  }

  public static boolean process(final int number) {
    System.out.println("processing number : " + number);
    return number % 3 == 0;
  }
}

Just for clarification the output is

computing number : 4
FALSE

As you can see this is the same output as the previous example showing that the same methods were executed in both. The compute and process methods are added to Lambda expressions which are stored in variables using the Supplier functional interface. The Supplier has a single method, get, which executes the Lambda that it represents.

Therefore when

computeResult.get()

Is called the function it represents is executed which in turn calls and executes the compute method. As this returned false the if statement also evaluated to false and processResult.get is never called leaving the process method to never be executed.

Another example can be shown with the use of a Stream which also will not be executed straight away.

public class LazyStreamExample {
  public static void main(String args[]) {

    final List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);

    System.out.println("DEFINING THE STREAM");

    final Stream<Integer> stream =
        numbers.stream()
            .filter(LazyStreamExample::compute)
            .filter(LazyStreamExample::process);

    System.out.println("NOT EXECUTED YET");

    final int result = stream.findFirst().orElse(0);

    System.out.println("THE RESULT IS : " + result);
  }

  public static boolean compute(final int number) {
    System.out.println("computing number : " + number);
    return number > 5;
  }

  public static boolean process(final int number) {
    System.out.println("processing number : " + number);
    return number % 3 == 0;
  }
}

Producing the follow output

DEFINING THE STREAM
NOT EXECUTED YET
computing number : 1
computing number : 2
computing number : 3
computing number : 4
computing number : 5
computing number : 6
processing number : 6
THE RESULT IS : 6

A Stream is created from the numbers list and stored within a variable. Like before with the Supplier it will not be executed until a dependent method is ran. In this example the findFirst method is used but unlike the Supplier, the Stream has many more methods that can execute it such as: collect, findAny and count.

Well done if you have made it to the end of this post (even thought it is short) and you have proved you are not lazy yourself… Or you scrolled to the end without actually reading it, in which case you are lazy. Anyway through the use of some lazy Lambdas we can keep our code easy to read without sacrificing performance by executing unneeded operations.

Dan Newton
Written by Dan Newton
Twitter
LinkedIn
GitHub