Java 8 Lambda Expressions

Jeff Bowman
Polyglot programmer, likes functional languages; lisp is cool - so is Clojure, Scala is nice… mostly, Groovy isn’t so much, [SML, OCaml, Haskell] are elegant languages, python and ruby are fun, javascript isn't - but coffeescript and especially clojurescript make it tolerable.
Jeff Bowman

Latest posts by Jeff Bowman (see all)

Jeff Bowman

Lambda Expressions are one of the more anticipated features of the newest Java release, and there has already been a lot of press coverage about them. Here I will explore a couple simple concepts related to them.

First, it is helpful to know what they are not. Lambda expressions are not:

  • Higher order functions
  • First class functions
  • Functions of any type

Lambda expressions are simply syntactic sugar for creating instances of an interface. In Java 8, they give it a fancy name: Functional Interface. A Functional Interface is a Java interface with exactly one abstract method. Java 8 also allows executable code to exist in interfaces now and those must be marked as default.

Here is a simple example:

Example 1

Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello world!");
    }
};

This contrived example should look fairly familiar. Nevermind if you haven’t used a Runnable before, just look at the shape of the code. There is a lot of “noise” around the actual executable line (the “Hello world!” line). With Java 8, this can all be shortened to the following:

Runnable r = () -> System.out.println("Hello Lambda Expression!");

That is a little nicer for sure. Both examples work out to be essentially the same thing. Calling either would look like this:

r.run();

This is because r regardless of which example is used above, is simply an instance of the interface Runnable. So the method must be explicitly called when you want the code to execute.

Now, let’s take a look at some of the functional interfaces found in the java.util.function. I won’t be looking at all of them, I’ll leave that as an exercise for the reader to investigate.

  • Predicate<T>
  • Function<T, R>
  • Supplier<T>
  • Consumer<T>

Predicate<T>
A Predicate tests values and returns a true or false value. There are also default methods for combining predicates with or and and. A predicate can also have negate applied to it which will invert the return value. Each of those methods will take a Predicate and return one. Here are some examples:

To call any of these, use the test method:

boolean result = notNullNorEmpty.test("should be true"); // --> true
result = notNullNorEmpty.test(null); // --> false

Function<T, R>
A Function accepts a parameter of type T and returns a result of type R. I think of this like a transformer. For example, I would like to transform a string to an integer, but I might have some rules which need to be met before I perform the transformation. Here is an example:

Notice in this example I chose to use braces to delimit the code. Multi-line lambda expressions will need braces as well as an appropriate return statement. I could have written this as:

To call a Function, use the apply method like this:

int i = only3PositiveDigits.apply("123"); // --> i = 123
i = only3PositiveDigits.apply("12345"); // --> i = -1

Supplier<T>
A Supplier supplies a value of type T. The value does not have to be the same each time. Here are a couple of examples:

As you can see, the Supplier interface has the abstract method get. The first example will always return 1; The second example will return an ever increasing positive number (at least until the number overflows).

Consumer<T>
A Consumer, unlike the rest of the interfaces does not have a return value. It exists for the side-affects. It consumes the value provided to it. A Consumer might be used for writing to files or logging or updating a mutable data structure like a List or Map. The abstract method to use is accept.

Here, the example is to log a message or throw an exception, or both.

log.accept("something to log"); // --> logs at the info level
toss.accept("something went wrong"); // --> throws an IllegalArgumentExecption
logAndToss.accept("something to log that went wrong"); // --> logs first, then throws an IllegalArgumentException

So, as you can see, using lambda expressions can help reduce the amount of code you write by removing the boilerplate noise surrounding the actual intent of the code. Some final thoughts to remember:

  • Lambda Expressions are syntactic sugar for creating instances of a Functional Interface — never forget this.
  • Multiple parameters require parens when defining the lambda expression: BiFunction<T, U, R> x = (a, b) -> … ;
  • No parameters also requires parens when defining the lambda expression (see the Runnable example at the top of the post)
  • Checked exceptions must be handled inside the braces defining your lambda expression – remember, you are defining an instance of an interface, so the scope of your try/catch must be within that block of code, not around it.

  • Throwing an exception requires braces (see the Consumer toss example above)

1 Comment:


  • By Jeff Bowman 27 May 2014

    Apologies for the rendering of the gists. WordPress doesn’t handle formatted code well, and the errors which might be visible are related to those issues… hence the gists. However if you look at the raw gists, you will see the correct code

Leave a Reply



Twitter Feed

Latest Confluex Tweets


©2014 Confluex, Inc. All Rights Reserved.