I suspect that if you have been programming in Java for some time, you have probably already used functional interfaces. Interestingly, sometimes you used them without being fully aware of it. This component appeared in Java version 8 and is now very much liked by developers. The semantics itself (and the way it is used) resembles a standard function, known from the functional programming paradigm:
f(x) -> y
In the functional programming paradigm, computation is expressed in terms of mathematical functions that avoid mutable state and side effects. The result of a function is solely determined by its inputs. Invoking a functional multiple times with the same inputs will always return the same result.
First, let’s get to know the official definition: a functional interface is an interface with a single, abstract method. As such, it can be the target for a lambda expression or method reference. As a reminder: an abstract method is a method that has no body, that is, no definition. I would also like to tell you that the term abstract is quite important here. Even before the release of Java 8, each method in interfaces was considered as abstract by default.
Before I tell you more about the details, let’s see what an example functional interface looks like:
@FunctionalInterface
public interface MyInterface {
boolean isMyMethod();
}
In Java 8, all methods added in the interface are always public, so there is no need to add an access modifier (usually the IDE you are coding will suggest removing this modifier). The same situation is for abstract keyword. I indicated that it is about version 8, because as of Java 9 private methods are also allowed in interfaces.
The above interface is functional because has declared only one abstract method. As you can see, I’ve added annotation @FunctionalInterface which is not required. But generally, in my opinion it is worth adding that annotation in the code because it triggers a compile-time check that the interface does, in fact, satisfy the requirement. Thus if the interface has either zero abstract methods or more than one, you will get a compiler error.
Moreover, functional interfaces can also have default and static methods. Both of them have implementations, so the compiler doesn’t treat them as abstract methods – that’s why everything works! The following code snippet shows such an example:
@FunctionalInterface
public interface MyInterface {
boolean isMyMethod();
static int addOneToNumber(int number) {
return number + 1;
}
default void executeAnotherMethod() {
System.out.println("Executing method...");
}
}
I’d like to note that if we add a second (abstract by default) method to an interface, a compile-time error will be thrown because the annotation clearly marks this interface as functional.
What about interface inheritance? Generally, inheritance is an import pillar of Object Oriented Programming, because this mechanism allows members of one class to inherit from another. In the case of functional interfaces, the annotation @FunctionalInterfaces checks the current interface. So if one interface extends an existing functional interface and adds another abstract method, it is not itself a functional interface. Without the @FunctionalInterface annotation, this compiles, because it’s a standard interface. It cannot, however, be the target of a lambda expression.
Everything clear so far? Now, I am going to disrupt our ideology a bit. In theory, a functional interface can only have one abstract method, but there is one edge case. You can declare methods in Object as abstract in an interface, but that doesn’t make them abstract. Usually the reason for doing so is to add documentation that explains the contract of the interface. An example of such an exception can be the Comparator interface. It is a functional interface, even though it has not only the compareTo abstract method declaration, but also the equals method declaration. The rules for functional interfaces say that methods from Object don’t count against the single abstract method limit. For example, this is a valid functional interface:
@FunctionalInterface
public interface MyInterface {
int myFunction(int firstNumber, int secondNumber);
default int getValue(){
return 0;
}
public String toString();
public boolean equals(Object o);
}
While many interfaces in the Java standard library contain only a single, abstract method and are thus functional interfaces, there is a one package that is specifically designed to contain only functional interfaces that are reused in the rest of the library. That package is called java.util.function. Becoming familiar with these methods will make your job as a developer easier.
These interfaces fall into four basic categories, whose descriptions can be defined as follows:
Supplier
This interface is particularly simple. It takes no object as input, but returns an object. It does not have any static or default methods. It contains only a single, abstract method, T get(). The type of the returned object depends on what type parameter we use.
@FunctionalInterface
public interface Supplier<T> {
T get();
}
There are also suppliers of primitive types in java.util.function package. Following pre-defined Supplier functional interfaces are categorized as Supplier as all of these interfaces have the same behavior of supplying a result.
Supplier type | Description | Abstract method |
BooleanSupplier | Represents a supplier of boolean-valued results | boolean getAsBoolean() |
DoubleSupplier | Represents a supplier of double-valued results | double getAsDouble() |
IntSupplier | Represents a supplier of int-valued results | int getAsInt() |
LongSupplier | Represents a supplier of long-valued results | long getAsLong() |
The lambda expression on the right side of the assignment operator must match the function interface type of the variable specified on the left side. One of the primary use cases for Supplier s is to support the concept of deferred execution.
Examples of using the Supplier:
The findFirst method on Stream returns the first encountered element in an ordered stream.
Optional<T> findFirst();
The orElse method on Optional returns either the contained element, or a specified default.
public T orElse(T other) {
return value != null ? value : other;
}
The orElseGet method, however, takes a Supplier as an argument.
public T orElseGet(Supplier<? extends T> supplier) {
return value != null ? value : supplier.get();
}
The orElseThrow method in Optional , which takes a Supplier . The Supplier is only executed if an exception occurs.
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
Objects.requireNonNull only customizes its response if the first argument is null.
public static <T> T requireNonNull(T obj, Supplier<String> messageSupplier) {
if (obj == null)
throw new NullPointerException(messageSupplier == null ? null : messageSupplier.get());
return obj;
}
CompletableFuture.supplyAsync returns a Completable Future that is asynchronously completed by a task running with the value obtained by calling the given Supplier.
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
return asyncSupplyStage(ASYNC_POOL, supplier);
}
The Logger class has overloads for all its logging methods that takes a Supplier rather than just a string.
public void info(Supplier<String> msgSupplier) {
log(Level.INFO, msgSupplier);
}
Practical example:
@FunctionalInterface
public interface Developer {
String getStatus();
}
public class InterfaceDemo {
public static void main(String[] args) {
Developer developer = () -> "My status is ACTIVE";
String status = developer.getStatus();
System.out.println(status);
}
}
The lambda expression constructed above is an implementation of the Supplier interface with a precisely defined type parameter. Therefore, we can write them in a different form:
public class InterfaceDemo {
public static void main(String[] args) {
Supplier<String> supplier = () -> "My status is ACTIVE";
System.out.println(supplier.get());
}
}
Since it has a specific type, we can use it as Java’s rules allow. For example, we can use it as the type of a method parameter. In this way, we can declare the execution of the method code in one place, and then realistically execute it in a completely different place. The execution consists in calling the get method of the Supplier interface. This makes sense because, as we know, we are dealing with a supplier and this is also how the method works, which has no parameters, but only returns a specific value.
public class InterfaceDemo {
public static void main(String[] args) {
Supplier<String> supplier = () -> "This is status ACTIVE";
useSupplier(supplier);
}
public static void useSupplier(Supplier<String> supplier) {
System.out.println(supplier.get());
}
}
Consumer
It accepts an object on input, but returns nothing. The Consumer function interface has one abstract accept method and looks like this:
@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); };
}
}
The accept(T t) method takes a generic argument and returns void. There is another method in this interface as well, but it is not abstract.
The java.util.function package also contains primitive variations of Consumer, as well as a two-argument version. Following pre-defined Consumer functional interfaces are categorized as Consumer as all of these interfaces have the same behavior of consuming the passed value(s) and returning no result. You can use any of these based on number of arguments or data type.
Consumer type | Description | Abstract method |
IntConsumer | Represents an operation that accepts a single int-valued argument and returns no result | void accept(int x); |
DoubleConsumer | Represents an operation that accepts a single double-valued argument and returns no result | void accept(double x); |
LongConsumer | Represents an operation that accepts a single long-valued argument and returns no result | void accept(long x); |
BiConsumer<T, U> | Represents an operation that accepts two input arguments and returns no result | void accept(T t, U u); |
ObjDoubleConsumer<T> | Represents an operation that accepts an object-valued and a double-valued argument, and returns no result | void accept(T t, double value); |
ObjIntConsumer<T> | Represents an operation that accepts an object-valued and a int-valued argument, and returns no result | void accept(T t, int value); |
ObjLongConsumer<T> | Represents an operation that accepts an object-valued and a long-valued argument, and returns no result | void accept(T t, long value); |
Examples of using the Consumer:
Optional.ifPresent(Consumer<? super T> consumer)
Stream.forEach(Consumer<? super T> action)
Stream.peek(Consumer<? super T> action)
Practical example:
We will now create a consumer type functional interface. We just need to introduce a method, for example, void takeNumberAndPrint(int number), which takes an argument but returns nothing.
@FunctionalInterface
public interface Programmer {
void takeNumberAndPrint(Integer number);
}
public class InterfaceDemo {
public static void main(String[] args) {
Programmer programmer = System.out::println;
programmer.takeNumberAndPrint(100);
}
}
We can also write a lambda expression assigned to the underlying Consumer interface. Of course, the code in our lambda expression can be more elaborate (similar to supplier and the implementation of other functional interfaces). Then we enclose the entire code with braces, creating one block statement.
public class InterfaceDemo {
public static void main(String[] args) {
String programmerName = "Oskar";
Integer programmerAge = 25;
Consumer<Integer> printConsumer = number -> {
System.out.printf("Hello %s", programmerName);
boolean isEqual = programmerAge.equals(number);
System.out.printf("\nResult: %b", isEqual);
};
executeConsumer(printConsumer);
}
static void executeConsumer(Consumer<Integer> consumer) {
consumer.accept(100);
}
}
Predicate
It takes an object as input and returns true or false. Predicates are used primarily to filter streams. Given a stream of items, the filter method in java.util.stream.Stream takes a Predicate and returns a new stream that includes only the items that satisfy the given predicate.
The single abstract method in Predicate is boolean test(T t) , which takes a single generic argument and returns true or false. The complete set of methods in Predicate interface is given below:
@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);
}
static <T> Predicate<T> not(Predicate<? super T> target) {
Objects.requireNonNull(target);
return (Predicate<T>)target.negate();
}
}
Following pre-defined Predicate functional interfaces are categorized as Predicate as all of these interfaces have the same behavior of accepting argument(s) and producing a boolean result.
Predicate type | Description | Abstract method |
BiPredicate<T, U> | Represents a predicate (boolean-valued function) of two arguments | boolean test(T t, U u); |
DoublePredicate | Represents a predicate (boolean-valued function) of one double-valued argument | boolean test(double value); |
IntPredicate | Represents a predicate (boolean-valued function) of one int-valued argument | boolean test(int value); |
LongPredicate | Represents a predicate (boolean-valued function) of one long-valued argument | boolean test(long value); |
Examples of using the Predicate:
If a value is present, and the value matches the given predicate, returns an Optional describing the value, otherwise returns an empty Optional.
Optional.filter(Predicate<? super T> predicate)
Removes all elements of this collection that satisfy the predicate.
Collection.removeIf(Predicate<? super E> filter)
Returns true if all elements of the stream satisfy the given predicate. The methods anyMatch and noneMatch work similarly.
Stream.allMatch(Predicate<? super T> predicate)
Returns a Collector that splits a stream into two categories: those that satisfy the predicate and those that do not.
Collectors.partitioningBy(Predicate<? super T> predicate)
Practical example
Predicates are useful whenever a stream should only return certain elements. This recipe hopefully gives you an idea where and when that might be useful.
@FunctionalInterface
public interface Worker<T> {
boolean checkSomething(T t);
}
public class InterfaceDemo {
public static void main(String[] args) {
Worker<Integer> worker = x -> x == 5;
boolean result = worker.checkSomething(2);
System.out.printf("Result: %b", result);
}
}
Of course, we can also prepare our own more complex interface that will meet the given assumptions using the default Predicate functional interface:
public class InterfaceDemo {
public static void main(String[] args) {
Predicate<String> predicate = x -> {
boolean isOk = x.isEmpty();
System.out.println(isOk
? "The value meets the initial assumptions"
: "The value doesn't meet the initial assumptions");
return isOk == (x.length() > 10);
};
usePredicate(predicate);
}
public static void usePredicate(Predicate<String> predicate) {
predicate.test("Test");
}
}
Note that for function interfaces that return a value for which the lambda expression does NOT contain a block statement (multi-line code), we do not use the return keyword. We immediately enter what is to be returned. On the other hand, when using a block statement, return is required at the end of the statement.
Function
Finally, we got to the strongest functional interface. Its power comes from the fact that it has a method that allows it to take a value on the input, return a value on the output, and these values can be of virtually any type. It takes an object on input and returns an object on output. The functional interface java.util.function.Function contains the single abstract method apply , which is invoked to transform a generic input parameter of type T into a generic output value of type R.
@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;
}
}
Following pre-defined Function functional interfaces are categorized as Function as all of these interfaces have the same behavior of accepting argument(s) and producing a result.
Function type | Description | Abstract method |
BiFunction<T, U, R> | Represents a function that accepts two arguments and produces a result | R apply(T t, U u); |
DoubleFunction<R> | Represents a function that accepts a double-valued argument and produces a result | R apply(double value); |
DoubleToIntFunction | Represents a function that accepts a double-valued argument and produces an int-valued result | int applyAsInt(double value); |
DoubleToLongFunction | Represents a function that accepts a double-valued argument and produces a long-valued result | long applyAsLong(double value); |
IntFunction<R> | Represents a function that accepts an int-valued argument and produces a result | R apply(int value); |
IntToDoubleFunction | Represents a function that accepts an int-valued argument and produces a double-valued result | double applyAsDouble(int value); |
IntToLongFunction | Represents a function that accepts an int-valued argument and produces a long-valued result | long applyAsLong(int value); |
LongFunction<R> | Represents a function that accepts a long-valued argument and produces a result | R apply(long value); |
LongToDoubleFunction | Represents a function that accepts a long-valued argument and produces a double-valued result | double applyAsDouble(long value); |
LongToIntFunction | Represents a function that accepts a long-valued argument and produces an int-valued result | int applyAsInt(long value); |
ToDoubleBiFunction<T, U> | Represents a function that accepts two arguments and produces a double-valued result | double applyAsDouble(T t, U u); |
ToDoubleFunction<T> | Represents a function that produces a double-valued result | double applyAsDouble(T value); |
ToIntBiFunction<T, U> | Represents a function that accepts two arguments and produces an int-valued result | int applyAsInt(T t, U u); |
ToIntFunction<T> | Represents a function that produces an int-valued result | int applyAsInt(T value); |
ToLongBiFunction<T, U> | Represents a function that accepts two arguments and produces a long-valued result | long applyAsLong(T t, U u); |
ToLongFunction<T> | Represents a function that produces a long-valued result | long applyAsLong(T value); |
Examples of using Function
The most common usage of Function is as an argument to the Stream.map method.
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
If the specified key does not have a value, use the provided Function to compute one and add it to a Map.
V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)
This method generates a Comparator that sorts a collection by the key generated from the given Function.
<T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
An instance method, also used in sorting, that adds an additional sorting mechanism if the collection has equal values by the first sort.
<U extends Comparable<? super U>> Comparator<T> thenComparing(
Function<? super T, ? extends U> keyExtractor)
Practical example
@FunctionalInterface
public interface Slaver {
String executeTransformation(Integer number);
}
public class InterfaceDemo {
public static void main(String[] args) {
Slaver slaver = Object::toString;
String result = slaver.executeTransformation(100);
System.out.printf("Result: %s", result);
}
}
Now let’s see what the implementation of such an interface will look like using a lambda expression. What should get your particular attention is the designation of the two types. The first is the type of the object taken as a parameter, and the second is the type of the object returned by the function.
We’ll do something simple to start with. The function method will analyze the given number, evaluate the set of numbers it belongs to, and return the result as a string.
public class InterfaceDemo {
public static void main(String[] args) {
Function<Integer, String> function = number -> {
String info = "";
info = String.join(",", info, number % 2 == 0 ? "EVEN" : "ODD");
info = String.join(",", info, number >= 0 ? "POSITIVE" : "NEGATIVE");
return info;
};
useFunction(function);
}
public static void useFunction(Function<Integer, String> function) {
function.apply(10);
}
}
Here we could also use a more specific type of object that is IntFunction<String>. In general, these types of block instructions are structured similarly to methods, so we can easily implement quite complex algorithms here. Remember to add a semicolon after you close the statement. Otherwise the code will not compile.
Operators
Functional interfaces categorized under Operator are specialized Function where the passed argument and result are of the same type.
Following pre-defined Operator functional interfaces are there that can be used in place of Function interfaces if returned value is same as the type of the passed argument(s). If argument of that function and return type are the same there can be use UnaryOperator which is defined in java.util.function as well. Of course, there are also other similar interfaces like IntUnaryOperator, DoubleUnaryOperator or LongUnaryOperator, for which the input and output arguments are (as you might expected) int, double and long.
Operator type | Description | Abstract method |
BinaryOperator<T> | Represents an operation upon two operands of the same type, producing a result of the same type as the operands | R apply(T t, U u); |
DoubleBinaryOperator | Represents an operation upon two double-valued operands and producing a double-valued result | double applyAsDouble(double left, double right); |
DoubleUnaryOperator | Represents an operation on a single double-valued operand that produces a double-valued result | double applyAsDouble(double operand); |
IntBinaryOperator | Represents an operation upon two int-valued operands and producing an int-valued result | int applyAsInt(int left, int right); |
IntUnaryOperator | Represents an operation on a single int-valued operand that produces an int-valued result | int applyAsInt(int operand); |
LongBinaryOperator | Represents an operation upon two long-valued operands and producing a long-valued result | long applyAsLong(long left, long right); |
LongUnaryOperator | Represents an operation on a single long-valued operand that produces a long-valued result | long applyAsLong(long operand); |
UnaryOperator<T> | Represents an operation on a single operand that produces a result of the same type as its operand | R apply(T t); |
Practical examples
The above list of operators is quite large, so I will not give an example for each element in the table. Here we will describe a few randomly selected poems with specific examples and their implementation. Let me give you some examples that I found myself using recently.
UnaryOperator
The UnaryOperator<T> interface is a part of the java.util.function package.
BinaryOperator
The BinaryOperator<T> interface is a part of the java.util.function package. It is an interface that works on one and the same type that has the apply and andThen methods. It represents a binary operator which takes two operands and operates on them to produce a result. Additionally, this interface has two methods: minBy() and maxBy() which take as an argument objects of classes that implement the Comparator interface.
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {
public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
Objects.requireNonNull(comparator);
return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
}
public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
Objects.requireNonNull(comparator);
return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
}
}
BiConsumer
BiConsumer is a functional interface with one accept method that takes two parameters and returns the void type, and with one default method – andThen that allows you to perform operations on related BiConsumer objects:
@FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T t, U u);
default BiConsumer<T, U> andThen(BiConsumer<? super T, ? super U> after) {
Objects.requireNonNull(after);
return (l, r) -> {
accept(l, r);
after.accept(l, r);
};
}
}
@FunctionalInterface
public interface BiProgrammer {
void takeTwoValuesAndPrint(Integer first, Integer second);
}
public class InterfaceDemo {
public static void main(String[] args) {
BiProgrammer programmer = (x, y) -> System.out.printf("First: %s, Second: %s", x, y);
programmer.takeTwoValuesAndPrint(100, 30);
}
}
public class InterfaceDemo {
public static void main(String[] args) {
BiConsumer<Integer, String> consumer = (x, y) -> System.out.printf("Integer = %s \nString = %s", x, y);
useBiConsumer(consumer);
}
public static void useBiConsumer(BiConsumer<Integer, String> consumer) {
consumer.accept(123, "Your value");
}
}
BiFunction
BiFunction is a functional interface with one apply method that takes two parameters and returns a parametric type, and with one default method – andThen, which allows you to perform operations on related Function objects:
@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t, U u) -> after.apply(apply(t, u));
}
}
In this way, we’ve gone through the four basic categories of functional interfaces
10 maja, 2022, 12:26 am
There’s definately a lot to find out about this subject. I really like all the points you’ve made.
12 marca, 2023, 12:57 pm
Do you have any video of that? I’d love to find out some additional information.
19 marca, 2023, 11:53 am
Cool + for the post