Java Functional Interface

In this article, we’ll go through what Functional Interface is in Java and how you can use them.

1. What is a Functional Interface in Java?

Functional Interfaces were introduced in Java 8 and these Interfaces are annotated with @FunctionalInterface annotation. By definition, a Functional Interface is an Interface that has exactly one abstract method. This means that it can have as many static and default methods as possible and still be considered a Functional Interface.

Furthermore, you can implement a Functional Interface by using a Lambda Expression, a Method reference, or a Constructor reference. Let’s go through the valid syntaxes:

1.1 One-Line Lambda

//no parameters syntax
() -> void_or_return_something
// one parameter
a_param -> void_or_return_something
// two or more parameters
(param_1, param2, param_3, ... , param_n) -> void_or_return_something

1.2 Method and Constructor Reference

// zero, one or more parameters
A_class_name_or_object::void_or_with_return_type_method
// constructor reference
A_class_name::new

A method reference can replace on specific occasions the lambda expression syntax.

1.3 Multiline Lambda

//no parameters syntax
() -> {
//lines_of_code
void_or_return_something;
};
// one parameter
a_param -> {
//lines_of_code
void_or_return_something;
};
// two or more parameters
(param_1, param2, param_3, ... , param_n) -> {
//lines_of_code
void_or_return_something;
};

2. Functional Interfaces Introduced in Java 8

In this section, we’ll go through the most common Functional Interfaces, all of which are under java.util.function package.

2.1 Consumer Functional Interface

Consumer just accepts one parameter and returns nothing. It has two methods:

  • void accept(T t)
  • default Consumer<T> andThen(Consumer<? super T> after)

The second method allows you to chain consumers. Let’s see a common use case for Consumer:

Stream.of("Code", "Learn", "Hub")
        //.forEach(System.out::println); this is exactly the same
          .forEach(s -> System.out.println(s));

Consumer<String> printer = System.out::println;
Consumer<String> printAgainWithStars = s -> System.out.println(s+"*");

Stream.of("C", "L", "H")
        .forEach(printer.andThen(printAgainWithStars));

First, we implemented the Consumer Functional interface inside the forEach(). Then, we defined two Consumers, one which just prints every string, and another that prints each string with a star at the end. Finally, we passed the printer Consumer and chained it with the second printer. The above snippet will print the following:

Code
Learn
Hub
C
C*
L
L*
H
H*

2.2 Function Functional Interface

Function just accepts a parameter of type T and returns a value of type R. It has 4 methods:

  • R apply(T t): This method just applies the function given a value and returns a value R.
  • default Function compose(Function before): It applies the before Function before the function called compose is executed.
  • static Function identity(): This is a function that returns whatever it was given as a parameter.
  • default Function andThen(Function after): It applies the after Function after the function that called andThen is executed.

The most common use case of Function is when using the map method of the Stream class.

2.3 Supplier Functional Interface

Supplier just accepts no parameter and returns a value. It has only one method T get() which returns the result.

2.4 Predicate Functional Interface

Predicate accepts a parameter and returns a Boolean value. As you can infer from the name, It is mainly used to filter elements of a stream.

Predicate has 6 methods:

  • boolean test(T t): This method returns the result.
  • default Predicate and(Predicate other): It allows you to chain Predicates using the AND operator and returns a Predicate.
  • default Predicate negate(): It negates the Predicate.
  • default Predicate or(Predicate other): You can chain Predicates using the OR operator and returns a Predicate.
  • static Predicate isEqual(Object targetRef): You can directly check if the element is equal to another element.
  • static Predicate not(Predicate target): It allows you to chain Predicates using the NOT operator and returns a Predicate.

Let’s see Predicate in action:

Stream.of("Hello", "World!", "Code!", "LEARN!", "hub").
        filter(
                Predicate.isEqual("Hello")
                .or(
                        Predicate.not(s -> ((String) s).contains("!"))
                )
                .negate()

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

Here the condition we formed is the following equivalent to pre-Java 8 Logical Operators:

!(s.equals("hello") || !s.contains("!"))

Of course, the output is the following:

2.5 Operator Suffix Functional Interfaces

All these Functional Interfaces (e.g. BinaryOperator, UnaryOperator, etc.) have only one difference with Function Functional Interface that we mentioned before; they only have one type that both parameters and return type must match.

3. Creating Your Own Functional Interfaces

So now you know which Functional Interfaces exist but what if you need a Function that accepts three parameters? Of course, you can create your own custom functional interfaces; below you will find a TriFunction Functional Interface:

@FunctionalInterface
public interface TriFunction<T, U, Q, R>{

    R apply(T t, U u , Q q);
}

You can create a TriPredicate, TriConsumer, TriSupplier, or TripleOperator as well.

Now we will create a Record as shown below:

private record Person(String name, String surname, Integer age){};

Then, let’s say we need to concatenate all of a Person’s attributes, therefore, we should implement the TriFunction Functional Interface and use it to accommodate our needs:

TriFunction<String, String, Integer, String> function = (s1, s2, i1) -> s1 + s2 + i1;
Stream.of(
                new Person("Geo", "Pal", 26),
                new Person("Dim", "Tas", 35),
                new Person("Ion", "Mak", 30)
        ).map(p -> function.apply(p.name(), p.surname(), p.age()))
        .forEach(System.out::println);

As you can observe, there isn’t anything special about implementing TriFunction; we just need to accept 3 parameters and return the result. Thus, we are now able to use this function inside map method of stream and get the required result:

GeoPal26
DimTas35
IonMak30

4. Conclusion

To sum up, you should be able to use any of the Functional Interfaces under java.util.function package and create your own Functional Interfaces. You can find the source code on our GitHub page.

5. Sources

[1]: Package java.util.function – Oracle Help Center