Mario has posted 14 posts at DZone. You can read more from them at their website. View Full User Profile

Why We Need Lambda Expressions in Java - Part 1

03.27.2013
| 57843 views |
  • submit to reddit

Lambda expressions are coming to Java 8 and together with Raoul-Gabriel Urma and Alan Mycroft I started writing a book on this topic. Anyway apparently they are still encountering some resistance and not all Java developers are convinced of their usefulness. In particular they say that it could be a mistake to try to add some functional features to Java, because they fear that this could compromise its strong object oriented and imperative nature. The purpose of this article is to hopefully remove any further doubt and show clearly, with practical and straightforward examples, why it is not possible for a modern programming language to not support lambda expressions.

External vs. internal iteration


Let's start with something very simple, a list of integers:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
and a for cycle that iterates all the items in the list and prints them:
for (int number : numbers) {
    System.out.println(number);
}
Straightforward as much as common: I don't remember a single day of my more than decennial working life as Java developer when I haven't write at least one cycle like this. Simple as much as ... completely wrong. It remembers me a lot how my 2 years old daughter Sofia puts away her toys after having played with them. It goes on more or less in this way:

Me: "Sofia, let's put the toys away. Is there a toy on the ground"
Sofia: "Yes, the ball"
Me: "Ok, put the ball in the box. Is there something else?"
Sofia: "Yes, there is my doll"
Me: "Ok, put the doll in the box. Is there something else?"
Sofia: "Yes, there is my book"
Me: "Ok, put the book in the box. Is there something else?"
Sofia: "No, nothing else"
Me: "Fine, we are done"

This is exactly what we do everyday with our Java collections, but unfortunately the biggest part of us is not 2 years old. We iterate the collection externally, explicitly pulling out and processing the items one by one. It would be far better for me if I could tell to Sofia just: "put inside the box all the toys that are on the ground". There are two other reasons, why an internal iteration is preferable: first Sofia could choose to take at the same time the doll with one hand and the ball with the other and second she could decide to take the objects closest to the box first and then the others. In the same way using an internal iteration the JIT compiler could optimize it processing the items in parallel or in a different order. These optimizations are impossible if we iterate the collection externally as we are used to do in Java and more in general with the imperative programming.

So, why don't we iterate internally? I think this is only a bad mental habit caused by the lack of support of this pattern in the Java Collection Framework that in turn has been caused by the verbosity (creation of an anonymous inner class) that this implies in pre-8 Java. Something like this:
numbers.forEach(new Consumer<Integer>() {
    public void accept(Integer value) {
        System.out.println(value);
    }
});
Actually both the forEach method and the Consumer interface have been added in Java 8, but you can already do something very similar in Java 5+ using libraries like guava  or lambdaj . However Java 8 lambda expressions allow to achieve the same result in a less verbose and more readable way:
numbers.forEach((Integer value) -> System.out.println(value));
The lambda expression is made of two parts the one on the left of the arrow symbol (->) listing its parameters and the one on the right containing its body. In this case the compiler automatically figures out that the lambda expression has the same signature of the only non implemented method of the Consumer interface (that for this reason is called a functional interface) and treat the first as it was an instance of the second, even if the generated bytecode could potentially be different. The declaration of the types of the lambda expression arguments can be, in the biggest part of cases, inferred by the compiler and then omitted as it follows:
numbers.forEach(value -> System.out.println(value));
But we can rewrite this last statement even more concisely using a method reference, another feature introduced in Java 8. More in details in Java 8 it is possible to reference both a static and an instance a method using the new :: operator as in:
numbers.forEach(System.out::println);
In this way, with a process that in functional programming is known as eta expansion, the name of the method is "expanded" by the compiler in the method itself that, as we have already seen, has the same signature of the only abstract method of the Consumer functional interface and then can be in turn converted in an instance of it.

Passing behaviors, not only values


What we have seen in the former example is the main and possibly the only reason why lambda expressions are so useful. Passing a lambda expression to another function allow us to pass not only values but also behaviors and this enable to dramatically raise the level of our abstraction and then project more generic, flexible and reusable API. Let's reenforce this with a further example: starting with the usual list of Integer
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
we are requested to write a method that sums all the Integers in the list as for instance:
public int sumAll(List<Integer> numbers) {
    int total = 0;
    for (int number : numbers) {
        total += number;
    }
    return total;
}
The day after a manager comes to our cubicle and tells you that the business also requires to have a function that sums only the even number in the list. So what is the quickest thing we could do? Easy. Just copy and paste the former method and add to it the required filtering condition:
public int sumAllEven(List<Integer> numbers) {
    int total = 0;
    for (int number : numbers) {
        if (number % 2 == 0) {
            total += number;
        }
    }
    return total;
}
Another day, another requirement: this time they need to sum the numbers in the list again but only if they are greater than 3. So what could we do? Well, we could again copy and paste the former method and just change that boolean condition ... but it feels so dirty, isn't it? Now, following the "First Write, Second Copy, Third Refactor" principle it is time to wonder if there is a smarter and more generic way to do this. In this case implementing an higher-order function accepting together with the list also a Predicate (another functional interface added in Java 8) that defines how to filter the numbers in the list itself before to sum them up.
public int sumAll(List<Integer> numbers, Predicate<Integer> p) {
    int total = 0;
    for (int number : numbers) {
        if (p.test(number)) {
            total += number;
        }
    }
    return total;
}
In other words we are passing to the method not only the data (the list of numbers) but also a behavior (the Predicate) defining how to use them. In this way we can satisfy all the 3 requirements with a single more generic and then more reusable method:
sumAll(numbers, n -> true);
sumAll(numbers, n -> n % 2 == 0);
sumAll(numbers, n -> n > 3);
In the second part of this article  I will show other examples to demonstrate how lambda expressions can make our Java code more readable and concise. 

Published at DZone with permission of its author, Mario Fusco.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Cloid Green replied on Wed, 2013/04/03 - 11:28am

Never did understand what a Lambda expression was.  

Thank you, VERY precise!!

=:-)

Bean Duke replied on Wed, 2013/04/03 - 11:44am

I'm a yours-truly-next CS student with half a year of Java programming. I also learn Ruby for just myself and lambdas in Java are what I want now.

Mark Stein replied on Wed, 2013/04/03 - 3:14pm

Why not to use here the "internal iteration"? e.g.

public int sumAll(List<Integer> numbers, Predicate<Integer> p) {

int total = 0;
  numbers.forEach(value ->
    if (p.test(value)) {
total += value; } ) return total; }

Maurice Naftalin replied on Wed, 2013/04/03 - 4:53pm in response to: Mark Stein

That code won't compile. The reason (apart from a pair of missing braces) is that total is not "effectively final" — an effectively final variable is one that could be declared final without changing the meaning of the program. The justification for this decision is written up in the Lambda FAQ here: http://goo.gl/XmWkY, but the short version is that since your code couldn't be parallelized, you're being discouraged from writing it.

Mark Stein replied on Wed, 2013/04/03 - 5:38pm in response to: Maurice Naftalin

understood 

Lalaka Jeerasinghe replied on Thu, 2013/04/04 - 1:16am

 That was very clear and concise

Mario Fusco replied on Thu, 2013/04/04 - 2:20am in response to: Mark Stein

Just because I explained internal iteration immediately before and in that second paragraph I wanted to show a different concept, i.e. that passing a lambda expression to a method corresponds to pass a behaviour instead of a data. I believe that explaining one single feature at time in isolation makes it more understandable, but this is just a personal opinion.

Anyway if you want to rewrite that method using internal iteration there's a far easier and more readable way using Streams ;) (see the second part of the article):

public int sumAll(List<Integer> numbers, Predicate<Integer> p) {
  return numbers.stream().filter(p).reduce(0, (a, b) -> a+b);
}

Greg Totsline replied on Sat, 2014/02/15 - 8:19am

Thanks for the article - nice examples that make more sense than other examples I've seen.

Tim Rockford replied on Fri, 2014/02/28 - 7:50pm

Wow, I am ecstatic as any C# developer would be. Java .. after 6 years finally saw the light and copied the Lambda expressions. Let's hope that JPA will use it for queries (to follow the EntityFramework's lead). Fantastic article - Kudos!

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.