The term "Functional Interface" was coined in the release of Java 8, specifically as part of the lambda expression feature that Java 8 introduced.
Pre Java 8, there was no consistent terminology for Interfaces with only one method, or Interfaces which were used to pass methods around as variables.
With the introduction of lambda functions in Java 8, the term was formalised and an annotation was created to enforce rules for what defines a Functional Interface so the Compiler or JVM would know which method in the Interface the lambda overrides.
Functional Interfaces critically must only have 1 abstract method. This will be the “Function” that the Interface represents./
We can enforce this with the @FunctionalInterface annotation. This, although not compulsory, will tell the compiler that the following Interface should be a Functional Interface and will fail to compile if there are more than 1 abstract methods.
static and default methods are allowed inside functional interfaces.
Why? static methods belong to the type itself, not instances so the compiler knows it’s not the function that the Interface is ‘carrying’ default methods already have an implementation, so the compiler also knows that it is not the function that the Interface carries!
private these methods are not accessible outside of the Interface anyways, so it doesn’t conflict with the function that the Interfaces carries!
Built-ins
Along with the @FunctionalInterface annotation, Java 8 also included a bunch of Functional Interfaces in the java.util.function package to give us standard ‘function types’ to use.
Here are some notables ones:
Predicate<T>:
Represents a boolean-valued function of one argument. Its abstract method is boolean test(T t).
Consumer<T>:
Represents an operation that accepts a single input argument and returns no result. Its abstract method is void accept(T t).
Function<T, R>:
Represents a function that accepts one argument and produces a result. Its abstract method is R apply(T t).
Supplier<T>:
Represents a supplier of results. It takes no arguments and returns a result. Its abstract method is T get().
UnaryOperator<T>:
Represents an operation on a single operand that produces a result of the same type as its operand. It extends Function<T, T>.
BinaryOperator<T>:
Represents an operation upon two operands of the same type, producing a result of the same type as the operands. It extends BiFunction<T, T, T>.
It also went back and annotated existing Interfaces as Functional Interfaces such as:
Runnable:
Represents a task that can be executed by a thread. Its abstract method is void run().
Callable<V>:
Represents a task that returns a result and may throw an exception. Its abstract method is V call().
Comparator<T>:
Represents a comparison function, which imposes a total ordering on some collection of objects. Its abstract method is int compare(T o1, T o2).
Implementation
There’s 2 main ways to implement Functional Interfaces
Anonymous Classes are discouraged for implementing Functional Interfaces!
Although Anonymous Classes can be used to implement Functional Interfaces, they are discouraged and should only be used to implement Non-Functional Interfaces with multiple methods.
Lambdas and Method Reference are distinctly different from Anonymous Classes as they don’t result in a new Class file being generated. Behind the scenes they are either a direct method reference via a MethodHandle or if the lambda is sufficiently complex, the JVM will create a synthetic Class in-memory at runtime to implement the lambda function and bind it to the Functional Interface using MethodHandle. Subsequently the JVM can use various optimisations unique to how MethodHandle-based invocation works, sometimes resulting in the complete removal and in-lining of the lambda.
This gives many added advantage over Anonymous Classes:
compile times are slightly quicker (less .class files generated)
smaller Bytecode (less .class files generated)
program start time is slightly faster
less linking of .class files (less I/O bottlenecks)
lambdas are lazy loaded as they are needed
caching of equivalent lambda instances (JVM can reuse instances for stateless lambdas)
better JVM optimisations (due to how MethodHandle-base invocation works)
faster runtime performance (when lambdas are in-lined)
Lambda Functions
One of the most common ways to use lambdas is in sorting a collection.
We can do this as List<E>’s sort(Comparator<? super E> c) method takes in a Comparator! Comparator<T> is a Functional Interface with the following abstract function int compare(T o1, T o2);.
Since it is a Functional Interface, we can use a lambda expression! We just need to make sure that our
lambda follows the method signature, taking in two variables and returning an int.
// given a collection of StringsList<String> names = Arrays.asList("Bob", "Alice", "Charlie", "Jack" "Eve", "David");// sort in-place with a lambda expression to define the sorting criterianames.sort((a,b) -> a.compareTo(b)); // sort assending names.sort((a,b) -> b.compareTo(a)); // sort decendingnames.sort((a,b) -> a.length - b.length); //sort by length
Method Reference
Whenever you see really simple lambda expressions, like the ones above that effectively just call a single method. You should use Method References instead! Method references could have better performance as they can easily be directly converted into a MethodHandle, however, modern JVMs are good enough do the same for simple lambdas.
Let’s convert the lambdas above to Method References
names.sort(String::compareTo); // sort assending // It is not possible to sort decending directly as there is no method for thatnames.sort(Comparator.comparing(String::length)); //sort by length
You can also leverage any existing method that matches the functional interface signature, making your code more concise and potentially more performant while improving readability by clearly indicating which operation is being performed.
// Class with custom business methodspublic class OrderProcessor { // This method happens to match the Predicate Functional Interface signature public boolean isValidOrder(Order order) { /* validation logic */ } // This method happens to match the ToDoubleFunction Functional Interface signature public double calculateDiscount(Order order) { /* discount logic */ }}OrderProcessor processor = new OrderProcessor();List<Order> orders = getOrders();// Filter valid ordersList<Order> validOrders = orders.stream() .filter(processor::isValidOrder) // Instead of: o -> processor.isValidOrder(o) .collect(toList());// Calculate discountsList<Double> discounts = orders.stream() .mapToDouble(processor::calculateDiscount) // Instead of: o -> processor.calculateDiscount(o) .boxed() .collect(toList());