Java 8 and JSR 335: Lambda Expressions (1/2)

This post is the first of a serie of two posts of Lambda Expressions and Streams. Take a look in the second part of the post clicking here

Introduction

Today I'll write a different kind of post. This post is based on my workshop dedicated to the Java 8 Lambda Expressions and Streams feature (specified in the JSR 335).

The point of this post is to be a summary of the content of the workshop where everyone who attended the workshop, a reference guide. For those who didn't have the change to attend I recommend you to take a look at the presentation (available as SpeakerDeck) and read this post following the presentation. The source code used in the presentation is available at GitHub.

It would be awesome to share some thoughts about these new features. I believe that this is the emerging of a new pattern of programming and in the next few years we'll be seeing a lot of new use cases to these features.

I'll split this post in two. One about Lambda Expressions and the other about Streams.

So let's get started!

Lambda Expressions

What is a lambda expression? In the 30's Alonzo Church created the Lambda Calculus. A formal system capable of represent any computable function. I won't discuss the details of Lambda Calculus but it has two important characteristics for us:

  1. It has a single transformation rule and function definition scheme
  2. All functions are anonymous

In the JDK 8 we call Lambda Expressions the definition of anonymous functions. Here is a simple example of a Lambda Expression in Java:

Consumer<String> consumer = (x) -> System.out.print(x);

After defining that I can call the method consumer.accept("Hello World!"); and it will apply the string parameter to the body System.out.print(x). The result will be a "Hello World!" String printed in the console.

We can think of it as a function defined in two parts:

  1. The right part of the expression is the body of the function
  2. The signature of the function is defined in a Functional Interface, in this case the Consumer<T> interface (more about Functional Interfaces below)

One important detail is that the lambda expression is of the same type of the functional interface associated with it.

Lambda Expression vs Anonymous Classes

There is a lot of discussion about lambda expressions and anonymous classes. The purpose of this post isn't demystify all they have in common or different. You just must know that they are different. Lambda expressions aren't a simpler way of writing anonymous classes. This doesn't mean that you can't use lambda instead of anonymous classes in some situations. Take a look on this example implementing a compression with an anonymous class:

Collections.sort(numbers, new Comparator<Integer>() {
        public int compare(Integer i1, Integer i2) {
            return i1.compareTo(i2);
        }
});

And this is the same comparator implemented with a lambda expression:

Collections.sort(numbers, (i1, i2) -> i1.compareTo(i2));

Which one is simpler? We'll talk more about some diferences between lambda expressions and anonymous classes. The most important thing is remembering that they aren't the same.

Now let's continue. We'll be looking to more interesting usage examples of Lambda Expressions in this workshop.

Functional Interfaces

Now that we saw what is a lambda expression we should take a look on a functional interface. The definition of a Functional Interface is very straightforward:

A Functional Interface is any interface with a single abstract method.

There are two important details in this definition. The first one is about the word any. That means that the concept of functional interfaces are backward compatible, independent of the JDK version.

The second details is about the single abstract method. Starting with JDK 8 now we can define default methods on our interfaces, a default implementation to be used for all implementing classes of the interface if not overrode by them. Until the JDK 7 to achieve this behaviour usually we create an abstract class implementing the interface and defining the default implementation for a method.

Catching up: a functional interface is a interface with one abstract method (without implementation) and zero or more default methods.

The example below shows an example of a simple functional interface. The @FunctionalInterface annotation is optional.

@FunctionalInterface
public interface Foo {
    String formatName(String name);
    
    default void salute(String name) {
        System.out.print("Hello, " + formatName(name));
    }
}

Now let's take a closer look at default methods and how they behave...

Default Methods

As I said (wrote :P) a default method is a way to add new methods on an interface without forcing every implementing class to implement this method. The focus of this feature is backward compatibility.

The method resolution of default methods works as for normal inheritance, choosing the closest one. If a class implement two interfaces with clashing default methods names (method resolution conflict) the class must override the default method. If the class needs to reference a specific default method it can reference it using Interface.super.defaultMethodName();.

Here is a example of resolving conflicts and still using the default implementation of both interfaces:

public interface A {
    void execute() {
        ... //do something
    }
}

public interface B {
    void execute() {
        ... //do other thing
    }
}

public class Foo implements A, B {
    @Override
    void execute() {    //not overriding this method throws a compiler error
        A.super.execute();
        B.super.execute();
    }
}

A important detail about default methods is that they have access to this. This can cause some trouble if used without attention:

public interface StackOverflowInterface {
    default void method1() {
        this.method2();
    }

    default void method2() {
        this.method1();
    }
}

Whether you call method1() or method2() you'll get a StackOverflowException!!!

Variable Capture

Lambda expressions can interact with variables outside it's body.

A local variable used in a lambda expression must be final or effectively final. The latter means a variable that isn't changed (the compiler can infer that it is final). For others variables scopes the same rule of anonymous classes applies.

A big difference between lambda expressions and anonymous inner classes is the use of the this keyword. Used inside an anonymous class it refers to the anonymous class. But inside a lambda expression the this refers to the outside object.

Method Reference

Lambda expressions are a way to define anonymous functions but do you like to rewrite a function every time you need it? That's where another JDK 8 feature excels, the method reference.

Using method reference we can use an existing function where a lambda expression is expected. The only restriction (that makes a lot of sense) is that the signature of the referenced method must match the signature of the functional interface. You can even reference constructors and instance methods.

Let me show you an example of method reference. Remember the first lambda expression in this post? Let's change it a little bit:

Consumer<String> consumer = System.out::print;

What do you think it will happen when we call consumer.accept("Hello World!");?

Well, the compiler will see that you are referencing the method public void print(String s) of the System.out so it matches the parameters and knows how to execute the method. You didn't have to explicit tell the compiler what are the parameters. It's the same to say: "I want to use the signature as defined in the functional interface, and for the implementation I want you to use the body of the function I'm referring". As expected the result will be a "Hello World!" String printed in the console.

Conclusion

The lambda expressions are one of the most exciting changes in the Java language so far and I'm really excited to see the use cases and patterns that will emerge from it. Also it helps a lot against the verbosity of anonymous classes.

That's the end of the first part of this post. I hope that it helps the understanding of these features and the underling concepts. In the next part we'll talk about operations on a sequence of elements supporting sequential and parallel aggregate operations, aka Streams.

See you there!

References

Show Comments