Declarative programming is another programming paradigm that focuses on describing what the program should accomplish rather than how to do it. It abstracts away the control flow.
Functional programming is a subset of Declarative programming in which desired computation is specified by composing functions. Composing functions is nothing but chaining two or more together by specifying how the output of one is fed as the input to the next. Functional programming makes implementing certain tasks easy and robust. Lambda Expression were introduced in Java 8 to allow Functional programming techniques in Java.
A functional interface is an interface with only a single abstract method. Abstract methods are the methods that do not have any body but only method declaration.
Example of Functional Interface — The Comparable
interface is a functional interface as it only has one abstract method that is *compareTo(T o)*
From Java 8, Interfaces can also have implementation. The methods that provide the implementation are known as default methods. They were introduced to provide backwards compatibility so that existing interfaces can use lambda expressions without implementing the methods in the implementation class. For example — forEach()
is a default method added to Iterable<T>
in Java 8. It allows collections (like List
) to use lambda expressions for iteration without implementing forEach()
in every class.
Default methods are also known as defender methods or virtual extension methods.
An example of the functional interface —
public interface Printer {
// abstract method
void print(String message);
// default method
default void printTwice(String message) {
System.out.println(message + " " + message);
}
}
We can optionally mark this interface with the @FunctionalInterface
annotation which will ensure that this method should contain only one abstract method.
Note — A functional interface can have any number of default methods.
If we need to inject Printer
as a dependency then we need to create an implementation of it.
public class ConsolePrinter implements Printer{
@Override
public void print(String message) {
System.out.println(message);
}
}
Now that we have created an implementation of the interface, we can inject it in any class that demands it. This design treats methods as second class citizens — saying that they cannot exists on their own and if they need an implementation then they need to be inside a class. It makes the code verbose, harder to read and maintain and also limits Java from efficiently supporting functional programming capabilities.