Java Lambda Functions: Function, Consumer, Supplier, Predicate

Let’s look at Lambda, which was introduced in Java 8.

What Is a Lambda Function?

Lambda function is a term used in programming languages to refer to anonymous functions. A lambda can be used temporarily and immediately, so it can be treated as a disposable function.

Basic Syntax

(argument) -> {processing}
()-> {processing}
(argument) -> {processing}
(argument) -> {return 0;}

You can use Lambda easily by connecting arguments and processing with ->. Let’s take a closer look through several examples below.

Basic Form

In Java 7 and earlier, thread code was usually written as follows.

new Thread(new Runnable() {
   @Override
   public void run() { 
      System.out.println("Hello world lambda"); 
   }
}).start();

When using a lambda, the code becomes shorter and much easier to read, as shown below.

new Thread(()->{
      System.out.println("Hello world lambda"); 
}).start();

Lambda with List

A normal for statement can be replaced with a lambda passed to the forEach function.

Suppose there is a list like the following.

List<String> stringList = new ArrayList<String>();
stringList.add("apple");
stringList.add("orange");
stringList.add("strawberry");

In Java 7 and earlier, you would have used a for statement like this.

for (String string : stringList) {
   System.out.println(string);
}

From Java 8 onward, you can write it as follows by using Lambda.

stringList.forEach(string -> System.out.println(string));

Because Lambda allows arguments and processing syntax to be omitted, you can write it in the shortest reasonable form.

The omission rules are as follows.

  • If the return value is void, return can be omitted.
  • If the processing code is one line, braces { } are not required, and the final semicolon ; can also be omitted.
  • If there is one argument, parentheses () can be omitted.
  • The data type of arguments can be omitted. There are two styles: omit all types or write all types.

There are other omission rules, but they will not all be covered here.

You can also write it using a method reference as follows.

stringList.forEach(System.out::println);

When method references are used together with Lambda, the code can be concise and easy to understand. However, if they are overused, the code can become harder to read, so it is better to use them only for explicit processing such as Objects::nonNull or XXXX::getId.

Lambda with Arrays

You may want to write the same kind of code for arrays, but arrays cannot use forEach directly, so they must be converted to a List first.

String[] stringArray = { "apple", "orange", "strawberry" };
Arrays.asList(stringArray).forEach(string -> System.out.println(string));
  • When using Lambda with arrays, you can also use the Arrays.stream() method.

Lambda with Map

With Map, you can apply Lambda directly.

Suppose there is a map like the following.

Map<String, String> stringMap = new HashMap<String, String>();
stringMap.put("apple", "red");
stringMap.put("orange", "orange");
stringMap.put("strawberry", "red");

In Java 7 and earlier, you would have used a for statement like this.

for (Entry<String, String> entry : stringMap.entrySet()) {
    System.out.println(entry.getKey());
    System.out.println(entry.getValue());
}

From Java 8 onward, you can write it as follows by using Lambda.

stringMap.forEach((key, value) -> {
    System.out.println(key);
    System.out.println(value);
});

Functional Interface

A functional interface is an interface that has exactly one abstract method. In other words, it is an annotation that lets functions be handled like first-class objects, and it restricts an interface so that it has only one abstract method.

First, create a functional interface.

@FunctionalInterface
public interface Math {
    int calc(int first, int second);
}

Now implement and run the interface method.

public class MathCalc {

    public static void main(String[] args) {
        Math plus = (first, second) -> first + second;
        System.out.println(plus.calc(3, 2));

        Math minus = (first, second) -> first - second;
        System.out.println(plus.calc(3, 2));
    }
}

Result:

5
5

Four Functional Interfaces

Java already provides commonly used functional interfaces. There are four main functional interfaces.

Functional interface Parameter Return value
Supplier<T> X O
Consumer<T> O X
Function<T, R> O O
Predicate<T> O Boolean

Supplier <T>

  • Supplier means “provider” in English, and it is a functional interface that has a return value without parameters.
  • It has T get() as its abstract method.

Supplier functional interface

package java.util.function;

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

Usage example

Supplier<String> supplier = () -> "Hello World!";
System.out.println(supplier.get());

Result:

Hello World!

Consumer<T>

  • Consumer means “consumer” in English. It is a functional interface that receives an object T as a parameter and uses it, but has no return value.
  • It has void accept(T t) as its abstract method.
  • It also provides a function called andThen, which allows the next Consumer to be chained after one function finishes.

Consumer functional interface

package java.util.function;

import java.util.Objects;

@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

Usage example

Consumer<String> consumer = (str) -> System.out.println(str.split(" ")[0]);
consumer.andThen(System.out::println).accept("Hello World!");

Result:

Hello
Hello World!

In the example above, the Consumer received by accept is processed first and displays “Hello”. Then the second Consumer received by andThen is processed and displays “Hello World!”. In functional programming, functions do not assign or change values, so even though the first Consumer changed the data with split, the original data is preserved.

Function<T, R>

  • Function means “function” in English. It is a functional interface that receives an object T as a parameter, processes it, and returns it as R.
  • It has R apply(T t) as its abstract method.
  • Like Consumer, Function provides andThen, and it additionally provides compose.
  • If andThen connects the next function to run after the first function executes, compose differs in that it connects a function to run before the first function executes.
  • There is also an identity function, which is a static function that returns itself.

Function functional interface

package java.util.function;

import java.util.Objects;

@FunctionalInterface
public interface Function<T, R> {

    R apply(T t);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

Usage example

Function<String, Integer> function = str -> str.length();
int length = function.apply("Hello World!");
System.out.println(length);

Result:

12

Predicate<T>

  • Predicate means “to assert as true”. It is a functional interface that receives an object T as a parameter, processes it, and returns a Boolean.
  • It has boolean test(T t) as its abstract method.

Predicate functional interface

package java.util.function;

import java.util.Objects;

@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }

    @SuppressWarnings("unchecked")
    static <T> Predicate<T> not(Predicate<? super T> target) {
        Objects.requireNonNull(target);
        return (Predicate<T>)target.negate();
    }
}

Usage example

Predicate<String> predicate = (str) -> str.equals("Hello World!");
boolean test = predicate.test("Hello World!");
System.out.println(test);

Result:

true