A functional interface is characterized by having precisely one abstract method within it. While it can include numerous default and static methods, it is restricted to having only a single abstract method. Additionally, it has the ability to define methods from the object class.
Java, renowned for its flexibility and widespread adoption, has been steadily progressing towards incorporating contemporary programming principles. A significant shift in recent times has been towards functional programming, drawing inspiration from languages like Scala, Haskell, and Clojure. Functional design presents an alternative methodology for software creation, emphasizing the primacy of immutable functions and data structures. This evolution is further supported by the incorporation of user interfaces within Java.
What are Functional Interfaces?
In Java, a functional interface refers to an interface that contains just a single abstract method. In contrast to conventional interfaces that are used to implement classes, functional interfaces emphasize behavior and provide developers with the ability to handle tasks as entities. They are fundamental to Java programming capabilities, facilitating the adoption of lambda expressions and practices.
Functional Interface, also referred to as Single Abstract Method Interfaces or SAM Interfaces, is a recent addition in Java that facilitates the implementation of a functional programming paradigm.
The debut of functional interfaces in Java 8 marked a significant shift, offering developers a more succinct and clear approach to writing code. These interfaces laid the foundation for embracing design patterns that are compatible with Java's environment.
Example
@FunctionalInterface
interface sayable{
void say(String msg);
}
public class FunctionalInterfaceExample implements sayable{
public void say(String msg){
System.out.println(msg);
}
public static void main(String[] args) {
FunctionalInterfaceExample fie = new FunctionalInterfaceExample();
fie.say("Hello there");
}
}
Output:
Hello there
A functional interface is capable of containing methods from the object class. This concept can be illustrated through the example provided below.
Example
@FunctionalInterface
interface sayable{
void say(String msg); // abstract method
// It can contain any number of Object class methods.
int hashCode();
String toString();
boolean equals(Object obj);
}
public class FunctionalInterfaceExample2 implements sayable{
public void say(String msg){
System.out.println(msg);
}
public static void main(String[] args) {
FunctionalInterfaceExample2 fie = new FunctionalInterfaceExample2();
fie.say("Hello there");
}
}
Output:
Hello there
Invalid Functional Interface
An interface that is functional is allowed to extend another interface as long as it does not contain any abstract methods.
Example
interface sayable{
void say(String msg); // abstract method
}
@FunctionalInterface
interface Doable extends sayable{
// Invalid '@FunctionalInterface' annotation; Doable is not a functional interface
void doIt();
}
Output:
compile-time error
Example 3
In this instance, a functional interface is being expanded to include a non-functional interface.
Example
interface Doable{
default void doIt(){
System.out.println("Do it now");
}
}
@FunctionalInterface
interface Sayable extends Doable{
void say(String msg); // abstract method
}
public class FunctionalInterfaceExample3 implements Sayable{
public void say(String msg){
System.out.println(msg);
}
public static void main(String[] args) {
FunctionalInterfaceExample3 fie = new FunctionalInterfaceExample3();
fie.say("Hello there");
fie.doIt();
}
}
Output:
Hello there
Do it now
Lambda Expressions and Method References
Functional interfaces are particularly effective when used in conjunction with lambda expressions and method references. Lambda expressions offer a succinct way to define anonymous functions, whereas method references enable developers to refer to pre-existing methods by their names.
Using lambda expression with Predicate
Predicate<Integer> isEven = (num) -> num % 2 == 0;
Using method reference with Consumer
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(System.out::println);
Developers can enhance the clarity and maintainability of their code by utilizing lambda expressions and method references. These functionalities facilitate a functional programming approach in Java, advocating for the utilization of immutable data and pure functions.
Java Predefined-Functional Interfaces
Java 8 presented a variety of pre-existing functional interfaces in the java.util.function package, each crafted to facilitate typical functional programming structures. Among the frequently utilized functional interfaces are:
Supplier: Represents a supplier of results.
Consumer: Denotes a function that takes one input and does not produce any output.
A predicate is a boolean-valued function that takes one argument.
Definition: Function refers to a block of code that takes a single input and generates an output.
UnaryOperator: This type signifies an operation that acts on a single operand and generates a result that matches the type of the operand.
BinaryOperator is a class that signifies a function performed on two inputs of identical types, resulting in an output of the same type as the inputs.
Functional interfaces in Java serve as a cornerstone for creating functional-style code. They encapsulate familiar patterns and empower programmers to craft code that is both more succinct and easier to comprehend.
Custom Functional Interfaces
In Java, there is a variety of functional interfaces available for developers to utilize, and they also have the ability to create custom function interfaces that cater to their individual requirements. This adaptability enables the customization of domain-specific functional interfaces to align with the demands of particular applications.
@FunctionalInterface
interface MathOperation {
int operate(int a, int b);
}
Developers use the @FunctionalInterface annotation to signify that an interface is designed for functional purposes. While not mandatory, this annotation acts as a form of documentation, providing clarity to other developers regarding the intended functionality of the interface.
It is possible to create our own custom functional interface. Provided below is a compilation of functional interfaces that are part of the java.util.function package.
| Interface | Description |
|---|---|
| BiConsumer<T,U> | It represents an operation that accepts two input arguments and returns no result. |
| Consumer<T> | It represents an operation that accepts a single argument and returns no result. |
| Function<T,R> | It represents a function that accepts one argument and returns a result. |
| Predicate<T> | It represents a predicate (boolean-valued function) of one argument. |
| BiFunction<T,U,R> | It represents a function that accepts two arguments and returns a a result. |
| BinaryOperator<T> | It represents an operation upon two operands of the same data type. It returns a result of the same type as the operands. |
| BiPredicate<T,U> | It represents a predicate (boolean-valued function) of two arguments. |
| BooleanSupplier | It represents a supplier of boolean-valued results. |
| DoubleBinaryOperator | It represents an operation upon two double type operands and returns a double type value. |
| DoubleConsumer | It represents an operation that accepts a single double type argument and returns no result. |
| DoubleFunction<R> | It represents a function that accepts a double type argument and produces a result. |
| DoublePredicate | It represents a predicate (boolean-valued function) of one double type argument. |
| DoubleSupplier | It represents a supplier of double type results. |
| DoubleToIntFunction | It represents a function that accepts a double type argument and produces an int type result. |
| DoubleToLongFunction | It represents a function that accepts a double type argument and produces a long type result. |
| DoubleUnaryOperator | It represents an operation on a single double type operand that produces a double type result. |
| IntBinaryOperator | It represents an operation upon two int type operands and returns an int type result. |
| IntConsumer | It represents an operation that accepts a single integer argument and returns no result. |
| IntFunction<R> | It represents a function that accepts an integer argument and returns a result. |
| IntPredicate | It represents a predicate (boolean-valued function) of one integer argument. |
| IntSupplier | It represents a supplier of integer type. |
| IntToDoubleFunction | It represents a function that accepts an integer argument and returns a double. |
| IntToLongFunction | It represents a function that accepts an integer argument and returns a long. |
| IntUnaryOperator | It represents an operation on a single integer operand that produces an integer result. |
| LongBinaryOperator | It represents an operation upon two long type operands and returns a long type result. |
| LongConsumer | It represents an operation that accepts a single long type argument and returns no result. |
| LongFunction<R> | It represents a function that accepts a long type argument and returns a result. |
| LongPredicate | It represents a predicate (boolean-valued function) of one long type argument. |
| LongSupplier | It represents a supplier of long type results. |
| LongToDoubleFunction | It represents a function that accepts a long type argument and returns a result of double type. |
| LongToIntFunction | It represents a function that accepts a long type argument and returns an integer result. |
| LongUnaryOperator | It represents an operation on a single long type operand that returns a long type result. |
| ObjDoubleConsumer<T> | It represents an operation that accepts an object and a double argument, and returns no result. |
| ObjIntConsumer<T> | It represents an operation that accepts an object and an integer argument. It does not return result. |
| ObjLongConsumer<T> | It represents an operation that accepts an object and a long argument, it returns no result. |
| Supplier<T> | It represents a supplier of results. |
| ToDoubleBiFunction<T,U> | It represents a function that accepts two arguments and produces a double type result. |
| ToDoubleFunction<T> | It represents a function that returns a double type result. |
| ToIntBiFunction<T,U> | It represents a function that accepts two arguments and returns an integer. |
| ToIntFunction<T> | It represents a function that returns an integer. |
| ToLongBiFunction<T,U> | It represents a function that accepts two arguments and returns a result of long type. |
| ToLongFunction<T> | It represents a function that returns a result of long type. |
| UnaryOperator<T> | It represents an operation on a single operand that returnsa a result of the same type as its operand. |
Conclusion
Functional interfaces revolutionize Java development by allowing programmers to embrace different programming paradigms within the language. They serve as the foundation for lambda expressions, enabling developers to craft code that is succinct, clear, and easily maintainable.
As Java progresses further, programming is poised to have a more significant impact on the language's future. Through adept utilization of business interfaces and adoption of programming principles, developers can unlock fresh opportunities and develop resilient and flexible software systems.
Java 8 Functional Interfaces MCQ
- Which of the following annotations is used to denote a functional interface in Java?
- @FunctionalInterface
- @Functional
- @Interface
- @Func
Explanation: The @FunctionalInterface annotation is used to denote a functional interface in Java. It ensures that the interface has only one abstract method.
- What is the purpose of a functional interface in Java?
- To provide multiple abstract methods for implementation by classes.
- To define a contract for classes that implement it.
- To enable the use of lambda expressions and method references.
- To enforce encapsulation and data hiding.
Explanation: Functional interfaces enable the use of lambda expressions and method references, providing a concise way to implement single abstract method interfaces.
- Which functional interface in Java's java.util.function package represents a function that accepts two arguments and produces a result?
- Consumer
- Function
- BiFunction
- Supplier
Explanation: The BiFunction interface in Java's java.util.function package represents a function that accepts two arguments and produces a result.
- Which functional interface in Java represents a function that accepts an argument and produces a result of the same type?
- UnaryOperator
- BinaryOperator
- Function
- Predicate
Explanation: The UnaryOperator interface in Java represents a function that accepts an argument and produces a result of the same type.
- What is the purpose of method references in Java?
- To provide a way to refer to methods without invoking them.
- To specify default implementations for methods in interfaces.
- To encapsulate behavior for use in lambda expressions.
- To allow calling methods in different threads simultaneously.
In Java, method references offer a way to reference methods without directly invoking them. They provide a concise syntax for lambda expressions, especially when the lambda expression only needs to call an already existing method.