Java 8 Lambda Expressions


Bhaskar S 10/12/2014


Lambda expressions are nothing more than anonymous functions.

So whats all the buzz around it ??? Hang in there - will illustrate the power of Lambda expressions shortly.

To execute any task in a thread, one usually implements the task as a Runnable.

The following code implements a simple task using Runnable:

Listing.1
/*
 * 
 * Name:   MyRunnable
 * 
 * Author: Bhaskar S
 * 
 * Date:   10/12/2014
 * 
 */

package com.polarsparc.java8;

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Hello Java 8 !!!");
    }
}

The following code executes the simple task:

Listing.2
/*
 * 
 * Name:   MyThreadTest
 * 
 * Author: Bhaskar S
 * 
 * Date:   10/12/2014
 * 
 */

package com.polarsparc.java8;

public class MyThreadTest {
    public static void main(String[] args) {
        Runnable r = new MyRunnable();
        
        Thread t = new Thread(r);
        t.start();
    }
}

Executing the program from Listing.2 will generate the following output:

Output.1

Hello Java 8 !!!

In the above example, we have two separate classes - one for the task and the other for executing the task. As more tasks are added, more task classes will be created and this simple application will get cumbersome to maintain.

The following code improves upon the previous example by eliminating the task class file by using an inner task class:

Listing.3
/*
 * 
 * Name:   MyThreadTest2
 * 
 * Author: Bhaskar S
 * 
 * Date:   10/12/2014
 * 
 */

package com.polarsparc.java8;

public class MyThreadTest2 {
    public static void main(String[] args) {
        Runnable r = new MyRunnable();
        
        Thread t = new Thread(r);
        t.start();
    }
    
    static class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("Howdy Java 8 !!!");
        }
    }
}

In the above example, as more inner task classes are added, this simple application will get large and hard to maintain.

The following code improves upon the previous example further by eliminating the inner task class by using an anonymous task class:

Listing.4
/*
 * 
 * Name:   MyThreadTest3
 * 
 * Author: Bhaskar S
 * 
 * Date:   10/12/2014
 * 
 */

package com.polarsparc.java8;

public class MyThreadTest3 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Hi Java 8 !!!");
            }
        });
        t.start();
    }
}

Can we further simplify the above example ???

The answer is YES !!!

This is where the Lambda expressions come into play.

The following code improves upon the previous example further by eliminating the anonymous task class by using a lambda expression:

Listing.5
/*
 * 
 * Name:   MyThreadTest4
 * 
 * Author: Bhaskar S
 * 
 * Date:   10/12/2014
 * 
 */

package com.polarsparc.java8;

public class MyThreadTest4 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> System.out.println("Hooray Java 8 !!!"));
        t.start();
    }
}

As can be seen from the code in Listing.5, the code using Lambda expressions is much more concise and easier to read and maintain.

In the above example, the piece of code new Thread(() -> System.out.println("Hooray Java 8 !!!") represents the Lambda expression.

In other words, a Lambda expression is an anonymous block of code that can be passed as an argument to another class or method for execution.

Lambda expressions can be used only in situations where the type is a functional interface. A functional interface is an interface that contains a single abstract method, as in the case of Runnable.

The general syntax for defining a Lambda expression is as follows:

(parameters) -> {block of code}

where:

Let us look at another example - sort a list of strings in alphabetical order.

The following code uses the Comparator functional interface (an interface that has only one method):

Listing.6
/*
 * 
 * Name:   MyCompareTest
 * 
 * Author: Bhaskar S
 * 
 * Date:   10/12/2014
 * 
 */

package com.polarsparc.java8;

import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

public class MyCompareTest {
    public static void main(String[] args) {
        List<String> names = new ArrayList<String>();
        
        names.add("zero");
        names.add("three");
        names.add("five");
        names.add("seven");
        names.add("nine");
        
        System.out.printf("Names <before>: %s\n", names);

        Collections.sort(names, new Comparator<String>() {
            @Override
            public int compare(String sa, String sb) {
                return sa.compareTo(sb);
            }
        });
        
        System.out.printf("Names <after>: %s\n", names);
    }
}

Executing the program from Listing.6 will generate the following output:

Output.2

Names <before>: [zero, three, five, seven, nine]
Names <after>: [five, nine, seven, three, zero]

The following code is a modified version of Listing.6 using Lambda expression that produces the same result as in Output.2:

Listing.7
/*
 * 
 * Name:   MyCompareTest2
 * 
 * Author: Bhaskar S
 * 
 * Date:   10/12/2014
 * 
 */

package com.polarsparc.java8;

import java.util.List;
import java.util.ArrayList;
import java.util.Collections;

public class MyCompareTest2 {
    public static void main(String[] args) {
        List<String> names = new ArrayList<String>();
        
        names.add("zero");
        names.add("three");
        names.add("five");
        names.add("seven");
        names.add("nine");
        
        System.out.printf("Names <before>: %s\n", names);

        Collections.sort(names, (sa, sb) -> sa.compareTo(sb));
        
        System.out.printf("Names <after>: %s\n", names);
    }
}

Since a Lambda expression (sa, sb) -> sa.compareTo(sb) is passed as the second argument to the Collections.sort() method, the compiler is able to determine the functional interface as Comparator and that the parameters sa and sb are of type String.

Java 8 introduces a default method called sort(Comparator<T>) in the List collection interface.

The following code is a modified version of Listing.7 using the sort() method from the List collection that produces the same result as in Output.2:

Listing.8
/*
 * 
 * Name:   MyCompareTest3
 * 
 * Author: Bhaskar S
 * 
 * Date:   10/12/2014
 * 
 */

package com.polarsparc.java8;

import java.util.List;
import java.util.ArrayList;

public class MyCompareTest3 {
    public static void main(String[] args) {
        List<String> names = new ArrayList<String>();
        
        names.add("zero");
        names.add("three");
        names.add("five");
        names.add("seven");
        names.add("nine");
        
        System.out.printf("Names <Before>: %s\n", names);
        
        names.sort((sa, sb) -> sa.compareTo(sb));
        
        System.out.printf("Names <After>: %s\n", names);
    }
}

Until now, in the examples demonstrated, we used Java's functional interfaces such as Runnable and Comparator. Nothing is preventing us from implementing our own functional interfaces.

Let us look at a example demonstrating custom functional interfaces.

The following code defines a custom functional interface to test for even numbers:

Listing.9
/*
 * 
 * Name:   EvenTester
 * 
 * Author: Bhaskar S
 * 
 * Date:   10/12/2014
 * 
 */

package com.polarsparc.java8;

@FunctionalInterface
public interface EvenTester {
    public boolean test(int n);
}

The use of the annotation @FunctionalInterface is optional and is for informational purposes only. The use of the annotation indicates the intended purpose of the interface and makes the code more readable.

The following code uses our custom EvenTester functional interface, creates a Lambda expression to check for even numbers, and passes it to a method for displaying the even numbers:

Listing.10
/*
 * 
 * Name:   NumEvenTester2
 * 
 * Author: Bhaskar S
 * 
 * Date:   10/12/2014
 * 
 */

package com.polarsparc.java8;

public class MyNumEvenTest2 {
    public static void main(String[] args) {
        int[] nums = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        
        printEvenNumbers(nums, (n) -> (n%2) == 0);
    }
    
    public static void printEvenNumbers(int[] nums, EvenTester tester) {
        System.out.print("Even #s: ");
        for (int n : nums) {
            if (tester.test(n)) {
                System.out.printf("%d ", n);
            }
        }
        System.out.print("\n");
    }
}

Executing the program from Listing.10 will generate the following output:

Output.3

Even #s: 0 2 4 6 8

As indicated earlier (when we illustrated the syntax for Lambda expression), the parameters for Lambda expressions are not typed. The question then arises - how can Lambda expressions leverage generic types ???

The answer - the functional interfaces associated with Lambda expressions can use generic types and hence implicitly leveraged by Lambda expressions.

Let us illustrate this with an example.

The following functional interface defines a generic number tester:

Listing.11
/*
 * 
 * Name:   NumberTester
 * 
 * Author: Bhaskar S
 * 
 * Date:   10/13/2014
 * 
 */

package com.polarsparc.java8;

@FunctionalInterface
public interface NumberTester<T> {
    public boolean test(T n, T d);
}

The following code uses our custom NumberTester functional interface to display numbers divisible by 4:

Listing.12
/*
 * 
 * Name:   MyDivideTest
 * 
 * Author: Bhaskar S
 * 
 * Date:   10/13/2014
 * 
 */

package com.polarsparc.java8;

public class MyDivideTest {
    public static void main(String[] args) {
        int[] nums = { 10, 20, 30, 40, 50, 60, 70, 80, 90 };
        
        printNumbers(nums, 4, (n, d) -> (n % d) == 0);
    }
    
    public static void printNumbers(int[] nums, int div, NumberTester<Integer> tester) {
        System.out.printf("Divisible by %s #s: ", div);
        for (int n : nums) {
            if (tester.test(n, div)) {
                System.out.printf("%d ", n);
            }
        }
        System.out.print("\n");
    }
}

Executing the program from Listing.12 will generate the following output:

Output.4

Divisible by 4 #s: 20 40 60 80

As can be seen from the above example, Lambda expressions are able to work very well with generic types.

Java 8 introduces additional generic functional interfaces in the package java.util.function.

In the above example, we can get rid of our custom functional interface and use the Java 8 built-in functional interface BiPredicate<T, U>.

This built-in functional interface represents a predicate that returns a boolean value and takes two generic arguments. The functional method defined is test(T arg1, U arg2).

The following code uses the built-in BiPredicate functional interface to display numbers divisible by 4:

Listing.13
/*
 * 
 * Name:   MyDivideTest2
 * 
 * Author: Bhaskar S
 * 
 * Date:   10/13/2014
 * 
 */

package com.polarsparc.java8;

import java.util.function.BiPredicate;

public class MyDivideTest2 {
    public static void main(String[] args) {
        int[] nums = { 10, 20, 30, 40, 50, 60, 70, 80, 90 };
        
        printNumbers(nums, 4, (n, d) -> (n % d) == 0);
    }
    
    public static void printNumbers(int[] nums, int div, BiPredicate<Integer, Integer> tester) {
        System.out.printf("Divisible by <%s> #s: ", div);
        for (int n : nums) {
            if (tester.test(n, div)) {
                System.out.printf("%d ", n);
            }
        }
        System.out.print("\n");
    }
}

There will be situations when one might want to reuse method(s) (static or instance) from existing class(es) as implementations for functional interfaces. In such scenarios, we will need a way to refer to those method(s) without executing them. This is where Method References come into play.

Method references are related to Lambda expressions since the target type of the method for re-use must be compatible with the functional interface type in the context where it is used.

Let us illustrate this concept with an example.

Assume we had a simple class for displaying text (as shown below) and is used by existing applications. It defines two methods - one is a static method and the other is an instance method.

Listing.14
/*
 * 
 * Name:   DisplayString
 * 
 * Author: Bhaskar S
 * 
 * Date:   10/13/2014
 * 
 */

package com.polarsparc.java8;

public class DisplayString {
    /* --- Instance Method --- */
    public void display1(String s) {
        System.out.println(s);
    }
    
    /* --- Static Method --- */
    public static void display2(String s) {
        System.out.println("[" + s + "]");
    }
}

The following is a simple program that displays the contents of a list using the class from Listing.14 above:

Listing.15
/*
 * 
 * Name:   MethodReferenceTest
 * 
 * Author: Bhaskar S
 * 
 * Date:   10/13/2014
 * 
 */

package com.polarsparc.java8;

import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;

public class MethodReferenceTest {
    public static void main(String[] args) {
        List<String> names = new ArrayList<String>();
        
        names.add("zero");
        names.add("three");
        names.add("five");
        names.add("seven");
        names.add("nine");
        
        System.out.println("Display using for-loop:");
        for (String s : names) {
            DisplayString.display2(s);
        }
        
        System.out.println("Display using iterator:");
        DisplayString obj = new DisplayString();
        Iterator<String> it = names.iterator();
        while (it.hasNext()) {
            obj.display1(it.next());
        }
    }
}

Executing the program from Listing.15 will generate the following output:

Output.5

Display using for-loop:
[zero]
[three]
[five]
[seven]
[nine]
Display using iterator:
zero
three
five
seven
nine

The following is the same code as in Listing.15, except that we are using method references:

Listing.16
/*
 * 
 * Name:   MethodReferenceTest2
 * 
 * Author: Bhaskar S
 * 
 * Date:   10/13/2014
 * 
 */

package com.polarsparc.java8;

import java.util.List;
import java.util.ArrayList;

public class MethodReferenceTest2 {
    public static void main(String[] args) {
        List<String> names = new ArrayList<String>();
        
        names.add("zero");
        names.add("three");
        names.add("five");
        names.add("seven");
        names.add("nine");
        
        System.out.println("Display using default method foreach (static):");
        names.forEach(DisplayString::display2);
        
        System.out.println("Display using default method foreach (instance):");
        DisplayString obj = new DisplayString();
        names.forEach(obj::display1);
    }
}

Notice the use of the newly added default method forEach(Consumer<T>) in Java 8 to the List collections interface.

The syntax of method reference to a static method of a class is - class-name::method-name and that of an instance method of a class is - object-reference::method-name.