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 2

04.01.2013
| 16994 views |
  • submit to reddit
In the first part of this article I started explaining, with hopefully straightforward examples, how the introduction of lambda expressions can make Java a more concise, readable, powerful and, in a word, modern language. I also announced that together with Raoul-Gabriel Urma and Alan Mycroft I started writing a book on this topic. In this second part I'll try to reenforce this idea with 2 more equally practical examples, hoping to help to convince all the Java developers, especially the most experienced ones, that we can no longer wait before to have a more functional oriented Java.

Efficiency through laziness


Another advantage of internal iteration of collections and more in general of functional programming is the lazy evaluation that it allows. Let me demonstrate this starting again from the same List of Integer I already used in the first part:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
This time let's suppose we want to achieve something just a bit more convoluted like to take only the even numbers in the list, double them and finally print the first one bigger than 5:
for (int number : numbers) {
    if (number % 2 == 0) {
        int n2 = number * 2;
        if (n2 > 5) {
            System.out.println(n2);
            break;
        }
    }
}
What is wrong with this solution should be clear: it does too much in a single, deeply nested and then difficult to read and maintain block of code. To fix this problem we can split the 3 operations performed by that code in 3 one-line methods each having a single well defined responsibility:
public boolean isEven(int number) {
    return number % 2 == 0;
}

public int doubleIt(int number) {
    return number * 2;
}

public boolean isGreaterThan5(int number) {
    return number > 5;
}
and rewrite the former code using them:
List<Integer> l1 = new ArrayList<Integer>();
for (int n : numbers) {
    if (isEven(n)) l1.add(n);
}

List<Integer> l2 = new ArrayList<Integer>();
for (int n : l1) {
    l2.add(doubleIt(n));
}

List<Integer> l3 = new ArrayList<Integer>();
for (int n : l2) {
    if (isGreaterThan5(n)) l3.add(n);
}

System.out.println(l3.get(0));
In practice I created a pipeline of list transformations where each stage uses one of the 3 methods operating on the single numbers. But is this code better than the former one? Probably no, and for two distinct reasons: the first and more obvious one is its verbosity, while the second is that it is computationally inefficient because it does more work than what is strictly necessary. To make more evident this second issue we can add a println in each of the 3 isEven, doubleIt and isGreaterThan5 methods, obtaining the following output:
isEven: 1
isEven: 2
isEven: 3
isEven: 4
isEven: 5
isEven: 6
doubleIt: 2
doubleIt: 4
doubleIt: 6
isGreaterThan5: 4
isGreaterThan5: 8
isGreaterThan5: 12
8
Here we can note that all the 6 numbers in the list get evaluated by our pipeline while the former nested for cycle evaluated only the first 4, being the 4th number the first that satisfies all the requested conditions.

Streams can help a lot to fix both this problems. You can create a Stream from any Collection by invoking the new stream() method on it. However Streams differ from Collections in several ways:

  • No storage: Streams don't have storage for values; they carry values from a data structure through a pipeline of operations.
  • Functional in nature: an operation on a stream produces a result, but does not modify its underlying data source. A Collection can be used as a source for a stream.
  • Laziness-seeking: many stream operations, such as filtering, mapping, sorting, or duplicate removal can be implemented lazily, meaning we only need to examine as many elements from the stream as we need to find the desired answer.
  • Bounds optional: there are many problems that are sensible to express as infinite streams, letting clients consume values until they are satisfied. Collections don't let you do this, but streams do.
Using a Stream we can solve the former problem with a single fluent statement:
System.out.println(
    numbers.stream()
            .filter(Lazy::isEven)
            .map(Lazy::doubleIt)
            .filter(Lazy::isGreaterThan5)
            .findFirst()
);
that produces the following output.
isEven: 1
isEven: 2
doubleIt: 2
isGreaterThan5: 4
isEven: 3
isEven: 4
doubleIt: 4
isGreaterThan5: 8
IntOptional[8]
Here we can note 2 things. First of all the laziness of the Stream allow us to don't waste CPU cycles and evaluate only the first 4 numbers in the List. In fact until, the second filter() invocation and before having called findFirst(), the Stream performs absolutely no operations because we haven't still asked a result to it. Second the findFirst() method doesn't return 8 but IntOptional[8]. An Optional is a wrapper of a value that can potentially doesn't exist at all. In other words in functional programming is common to use an Optional (the equivalent of Option in Scala or Maybe in Haskell) to model a value that couldn't be there instead of returning a null reference as we are traditionally used to do in Object Oriented Java. I already wrote another article where I explained in more details how Optional works and pointed out some limitations of the current Java 8 implementation suggesting an alternative one. In this particular case, since we are sure that there is a result, we can safely unwrap the Optional by invoking getAsInt() on it and obtain the result 8 as expected.

The loan pattern


To give one last example of how we can leverage functional programming in Java and in particular to show how we can achieve better encapsulation and avoid repetitions, let's suppose we have a Resource:
public class Resource {

    public Resource() {
        System.out.println("Opening resource");
    }

    public void operate() {
        System.out.println("Operating on resource");
    }

    public void dispose() {
        System.out.println("Disposing resource");
    }
}
that we can create, do something on it and, after having used it, we have to dispose in order to avoid leaks (of memory, file descriptors, etc.)
Resource resource = new Resource();
resource.operate();
resource.dispose();
What's wrong with this? Of course the fact that while operating on the resource we could get a RuntimeException so, in order to be sure that the dispose() method will be actually called, we have to put it in a finally block:
Resource resource = new Resource();
try {
    resource.operate();
} finally {
    resource.dispose();
}
The problem here is that we will have to repeat this try/finally block again and again every time we use the Resource (something that clearly violates the DRY principle) and in case we will forget it in some point of our code we will run the risk of having a leak. To fix this issue I suggest to encapsulate this try/finally block in a static method of the Resource class and possibly to make its constructor private in order to oblige the clients of that class to use it only through that method:
public static void withResource(Consumer<Resource> consumer) {
    Resource resource = new Resource();
    try {
        consumer.accept(resource);
    } finally {
        resource.dispose();
    }
}
The argument passed to this method is an instance of the Consumer functional interface that allows to consume the resource or, in other words, to perform some actions on it. In this way we can operate on the resource by just passing a lambda expression to that method:
withResource(resource -> resource.operate());
It guarantees that the resource will be always disposed correctly avoiding any repetition. Also it should be now clearer where the name of this pattern comes from: the "lender" (the code holding the resource) manages the resources once the "lendee" (the lambda expression accessing it) has finished to use it.

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

Kishore Naga replied on Wed, 2013/04/03 - 4:23pm

 Very well explained. Good to know Java is adopting the conciseness from Scala and other features like Option.

Charles Roth replied on Fri, 2013/04/26 - 11:01am

 I'm curious what the impact of lambda's will be on unit-testing in Java.

On one hand, the simpler expression (as in the code examples given) make for shorter, easier to read code.  (And probably shorter and easier to read unit-test code in particular!)

On the other hand, I can imagine a slippery slope where lambdas encourage wrapping more and more business logic in essentially anonymous methods... and thus making that logic more difficult to isolate and test.

Thoughts?  I have no opinion yet, but it looks like there could be some tension between the paradigms, so to speak.

Comment viewing options

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