Lambda Expressions

A lambda expression is an unnamed function. A lambda includes references to its surrounding scope (closure). Lambda expressions were introduced in Java 8.

The syntax of a lambda expression is:

    (Datatype variable, ...) -> { statement block; }

For example, a lambda that prints its argument to System.out is:

    (Object x) -> { System.out.println(x); }

Usually the datatype of parameters can be inferred from context, so you can omit them:

    (x) -> { System.out.println(x); }

If there is only one parameter, you can also omit the parenthesis:

    x -> { System.out.println( x ); }

If the lambda definition is only a single statement you can omit the brackets {…} and semi-colon, too. So the above becomes:

    x -> System.out.println( x )

Using Lambda Expressions

Lambdas can be used anyplace where you can use an object that implements an interface. You can assign it to a variable so you can invoke it later (perhaps many times).

Everything in Java has to have a type. Since a lambda defines a single method, the type of a lambda is an interface with a single abstract method.

For example: Comparator (abstract compare method), Runnable (run method), or Callable (call method).

Example using Anonymous class: Define a Comparator for Strings the compares strings ignoring case.

// define a Comparator for case-insensitive sorting of Strings
Comparator<String> ignoreCase = new Comparator<String>( ) {
    public int compare(String a, String b) {
        return a.compareToIgnoreCase(b);
    }
};
// sort an array of strings, ignoring case
Arrays.sort( array_of_string, ignoreCase );

A lot of code for a method containing one one statement.

Example 1 (Define a Lambda): define a Comparator as lambda expression instead of Anonymous Class.

// define a Comparator for case-insensitive sorting of Strings
Comparator<String> ignoreCase = (a,b) -> a.compareToIgnoreCase(b);

// sort an array of strings
Arrays.sort( array_of_string, ignoreCase );

Example 2 (Define Lambda at Point of Use): same as above, but define and use in one statement.

// sort an array of strings ignoring case
Arrays.sort( array_of_string, (a,b) -> a.compareToIgnoreCase(b) ); 

In both examples, the compiler can infer that a and b must by type String. The compiler can also infer that the expression should return an int because the compare method in the Comparator interface returns int.

Recommendation

If a lambda expression is more than one statement or you want use use it more than one place then define the lambda first (Example 1) and then use it. Don’t write long inline lambda expressions (like the event handler below).

Inline Lambda Definition is Hard to Understand:

// JavaFX event handler for a Button -- don't do this
button.setOnAction( (event) -> {
       String text = inputfield.getText().trim();
       if (text.isEmpty()) return;
       processInput(text);
       text.clear();
       } );

Functional Interfaces

A functional interface is an interface with only one abstract method, such as Runnable and Comparator. Java 8 adds many new functional interfaces in the package java.util.function. Here are a few:

Interface Abstract Method Use
Consumer<T> void accept(T t) Do something with the parameter, return nothing.
Function<T,R> R apply(T t) Unary function or mapping of T -> R.
Predicate<T> boolean test(T t) boolean condition
BinaryOperator<T> T apply(T a, Tb) binary operator
UnaryOperator<T> T apply(T a) unary operator, T -> T

Functional Interfaces for Primitives
There are many variants of the above interfaces for the case where a parameter or return value is a primitive (since primitives cannot be used as type parameter). For example:

Interface Abstract Method Use
IntFunction<R> R apply(int arg) Unary function from int to R.
ToIntFunction<T> int applyAsInt(T arg) Unary function from T to int
DoubleConsumer void accept(double arg) Perform operation on a double value.
DoubleUnaryOperator double applyAsDouble(double x) Function of double returing double.

Example:
We often need to test if a String is null or empty. Define a predicate named empty for this:

Predicate<String> empty = (String s) -> s == null || s.trim().isEmpty();
// Using the predicate:
String foo = "";
if (empty.test(foo)) System.out.println("Foo is empty string");

Exercise 1: Lambda Greeter

Write a Lambda expression for a Consumer to greet a person by name. It should print “Hello, name”.

Consumer<String> greeter = ____________________________________________;
// This should print "Hello, Paruj"
greeter.accept("Paruj");

Exercise 2: Compute Age in Years

In Java, to compute the age of a Person, using a LocalDate for birthday, we can use a java.time.Period like this:

LocalDate birthday = LocalDate.of(1999, 1/*Jan*/, 15);
LocalDate now = LocalDate.now();
// compute difference from birthday to now
Period age = Period.between(birthday, now);
System.out.println("age: " + age.getYears() );

Write a Lambda Expression named age that has a LocalDate parameter (a date) and returns the parameter’s age in years as an Integer. You need more than one statement for this, so write a block surrounded by {…}.

Function<LocalDate,Integer> age = (date) ->
                ____________________________________________
                ____________________________________________
                ____________________________________________
                ____________________________________________
// In 2018, this should by age 20
LocalDate bday = LocalDate.of( 1998, 1, 5);
System.out.printf("Birthdate %tF is age %d years\n", bday, age.apply(bday) );

// this should be age 19
LocalDate bday2 = LocalDate.of( 1998, 12, 31);
System.out.printf("Birthdate %tF is age %d years\n", bday2, age.apply(bday2) );

Rewrite the age lambda as a ToIntFuction so it returns an int.

ToIntFunction<LocalDate> age = date ->
                ____________________________________________
                ____________________________________________
                ____________________________________________
                ____________________________________________
// For 2018, this should print "age 20"
LocalDate bday = LocalDate.of( 1998, 1, 5);
System.out.printf("Birthdate %tF is age %d years\n", bday, age.applyAsInt(bday) );

What Were the Java Designers Thinking?

A good software API should be consistent. The method in the Function and IntFunction interfaces is apply(), but in ToIntFunction the method name is applyAsInt(). Many of the special-case interfaces for primitives are like this, but it is inconsistent.

Reference

Lambda Expressions in the Oracle Java Tutorial, part of https://docs.oracle.com/javase/tutorial/java/javaOO/.