Java Functional Interfaces Tutorial

A tutorial about Java Functional Interfaces. Learn the concept of Functional Interfaces and why they were added in Java 8 – with the help of code examples.

What is Java Functional Interface?

A Functional Interface is an interface that has just one Abstract method and thus represents a single-function contract. In other words, the Functional Interfaces facilitate only a single Function or a method.

Functional Interface is also called SAM (Single Abstract Method Interface). The thing to note: A Functional Interface can have a Single abstract method. However, it can have any number of Default Methods.

Functional or Not Functional?

The word Single is not so simple here. Because the ‘Single’ method can exist in the form of multiple abstract methods inherited from super interfaces. But, in that case, the inherited methods should logically represent a single method. Alternatively, it might redundantly declare a method provided by classes like Object, e.g., toString.

Now, let’s see some of the examples of interfaces and understand if they are Functional.

// Functional
interface Runnable {
    void run();
}


// Not functional; equals is already an implicit member
interface Foo {
    @Override
    boolean equals(Object obj);
}


// Functional; Bar has one abstract non-Object method
interface Bar extends Foo {
    int compare(String o1, String o2);
}


// Functional; Comparator has one abstract non-Object method
interface Comparator {
    boolean equals(Object obj);
    int compare(T o1, T o2);
}


// Not functional; method Object.clone is not public
interface Foo {
    int m();
    Object clone();
}


//------------------------
interface X {
    int m(Iterable arg);
}
interface Y {
    int m(Iterable arg);
}

// Functional: two methods, but they have the same signature
interface Z extends X, Y {}
Code language: Java (java)

Annotation @FunctionalInterface

I hope these examples help you understand which Interfaces are Functional Interfaces. Alternatively, you can use @FunctionalInterface annotation on the top of an Interface. However, this annotation doesn’t make your interface functional but throws a compilation error if your interface is not a ‘Functional interface.’

Functional Interface and Lambda Expressions

In Java 8 and onwards, the Lambda expressions can implement the function interfaces.

When a method or expression requires a functional type Interface, you can use Lambda syntax to provide an inline interface implementation.

@FunctionalInterface
public interface Runnable {
   public abstract void run();
}Code language: Java (java)

For example, the Runnable class in Java is a Functional Interface. And below is a traditional way you provide the anonymous inner class to provide its implementation.

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("I am running in separate thread");
    }
}).start();Code language: Java (java)

Below is how Lambda expression can implement the same interface in a short syntax.

new Thread(() -> System.out.println("I am running in separate thread")).start();Code language: Java (java)

In-built Functional Interfaces

Until now, I hope you are clear with the concept of Functional Interfaces and how Lambda expressions implement them. Java has provided some handy functional interfaces which are ready to use. Instead of creating one, you can use them in many places.

Function

The Function Interface is for applying specific transformations on the given object. Which has a single abstract method called as apply. It can take an argument of a type and can return another type.

public interface Function<T,U> {
    public <U> apply(T parameter);
}Code language: Java (java)

For example, Stream#map() accepts a Function implementation. Firstly, we will see an example of Anonymous implementation.

employees.stream().map(new Function<Employee, String>() {
       @Override
       public String apply(Employee e) {
           return e.getName();
       }
}).collect(Collectors.toList());Code language: Java (java)

With lambda, the above statement looks much more readable and straightforward.

employees.stream()
    .map(x -> x.getName())
    .collect(Collectors.toList());Code language: Java (java)

The syntax can be simplified using the Method Reference.

employees.stream()
    .map(Employee::getName)
    .collect(Collectors.toList());Code language: Java (java)

To sum up, a Functional interface can be used where an object or a value is being transformed – like the map method above – where the Stream of Employees is mapped into a Stream of Strings.

Consumer

This is one more pre-defined Functional Interface. As the name suggests, it defines a function that consumes the given parameter.

public interface Consumer <T> {
    void accept(T t);
}Code language: Java (java)

For example, Stream.forEach. Which is called once per element in the Stream and returns void. Let’s see how we can use Consumer implementation here.

employees.stream()
    .map(Employee::getName)
    .forEach(System.out::println);Code language: Java (java)

The Stream of Employee is first mapped to a Stream of Strings (employee names). After that, each name is printed inside the forEach method.

Predicate

The Predicate represents a function that evaluates the state of an object into Boolean value. The function accepts an object and returns a boolean.

public interface Predicate {   boolean test(T t); }Code language: Java (java)

For example, we can refer to Stream.filter method, which is used to filter out elements from the stream.

employees.stream()
    .filter(e -> e.getAge() >= 40)
    .collect(Collectors.toList());Code language: Java (java)

Here, the filter method is filtering out employees aged over 40 and collecting the rest in a list.

Supplier

The Supplier interface is to supply things. The Supplier function doesn’t accept any argument but can return an object of provided generic type.

public Interface Supplier<T>{
    T get();
}Code language: Java (java)

You cannot re-use Java Streams. In other words, you call a Terminal Operation on a Stream; the stream is dead.

Stream<Employee> empStream = Stream.of(new Employee("a", 43), new Employee("b",39));

// Terminal Operation is Called on the Stream
empStream.filter(emp -> emp.getAge() >= 40).forEach(System.out::println);

//Using same stream results in Runtime Exception
//Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed

empStream.forEach(System.out::println);Code language: Java (java)

In such cases, the supplier helps. Also, it is beneficial where you want to create a reusable data set—for example, mock datasets in tests.

Supplier<Stream<Employee>> supplier = () -> {Stream.of(new Employee("a", 43), new Employee("b", 39)};
        
supplier.get()
    .filter(emp -> emp.getAge() >= 40)
    .forEach(System.out::println);


supplier.get()
    .forEach(System.out::println);Code language: Java (java)

In the above example, both of the operations are possible. Because every time you use the Supplier, a new Stream is created.

Binary Operator

This BinaryOperator interface represents a function that takes two parameters and returns one. You can use it to define Mathematical operations like comparison, addition, etc.

For example, Stream#reduce() method takes a BinaryFunction. Using reduce, we will find the youngest employee in the stream.

empStream
    .reduce((x, y) -> x.getAge() <= y.getAge() ? x : y)
    .ifPresent(System.out::println);
Code language: Java (java)

Unary Operator

The UnaryOperator interface defines a function that takes one parameter and returns an object simultaneously. You can use this function to change the value of a given object. For example, you are finding the square of a number or converting a String to upper case.

List<Double> longs = Arrays.asList(1d, 2d, 3d, 4d, 5d);
//square of each number in the list
longs.replaceAll(l -> Math.sqrt(l));
//Or, using method reference
longs.replaceAll(Math::sqrt);Code language: Java (java)

Additionally, we will see an example of generating an infinite stream of sequential numbers using the Stream#iterate() method, which accepts a UnaryOperator. We will print only the first ten elements from the stream.

Stream
    .iterate(1, x -> x + 1)
    .limit(10)
    .forEach(System.out::println);Code language: Java (java)

Summary

This was Java Functional Interfaces Tutorial. Where we learned that Functional Interfaces have Single Abstract Method (SAM). They represent a single functional contract.

The Java 8 Lambda Expressions provide in-line implementations for the functional interfaces. Moreover, these in-line implementations are shorter and more straightforward than anonymous implementations. Also, we learned some of the in-built functional interfaces by Java, and you can re-use them in various situations.