Functional Interfaces in Java

Java, a versatile and widely used programming language, has evolved significantly over the years. One of the key additions in Java 8 was the introduction of functional interfaces. Functional interfaces bring a taste of functional programming to Java, allowing developers to write more concise and expressive code.

What is a Functional Interface?

A functional interface is an interface that contains only one abstract method. It may also contain multiple default or static methods, but the presence of a single abstract method is crucial. This unique characteristic makes functional interfaces well-suited for use with lambda expressions, which were also introduced in Java 8.

The @FunctionalInterface annotation was introduced to explicitly mark an interface as a functional interface. While not strictly necessary, this annotation helps prevent developers from adding more than one abstract method to the interface accidentally.

Why Functional Interfaces?

Functional interfaces play a pivotal role in the adoption of functional programming paradigms in Java. They enable the use of lambda expressions, which are concise representations of anonymous functions. Lambda expressions, in turn, facilitate the implementation of functional-style programming in Java, promoting readability and maintainability.

Benefits of Functional Interfaces

Functional interfaces offer several benefits, including:

  • Improved code readability: Functional interfaces make code more readable and concise by encapsulating a single behavior into a separate interface.

  • Increased code reusability: Functional interfaces can be easily reused in different parts of an application, reducing code duplication and improving maintainability.

  • Simplified code testing: Functional interfaces can be easily tested using lambda expressions, making code more testable and reliable.

Example of a Functional Interface

Let’s explore a few examples of functional interfaces and how they can be used with lambda expressions.

Runnable Interface

The Runnable interface is a classic example of a functional interface. It has a single abstract method, run(), which represents the code to be executed.

				
					@FunctionalInterface
interface MyRunnable {
    void run();
}

public class Example1 {
    public static void main(String[] args) {
        // Using lambda expression to implement the run method
        MyRunnable myRunnable = () -> System.out.println("Executing myRunnable");
        
        // Executing the run method
        myRunnable.run();            //Prints Executing myRunnable

    }
}

				
			

In the above example, The MyRunnable interface is declared as a functional interface using the @FunctionalInterface annotation. This annotation is optional but recommended to ensure that the interface follows the functional interface contract. The MyRunnable interface has a single abstract method, run(), which represents the block of code that will be executed.
In the main method, a lambda expression is used to provide an implementation for the run() method. 

The lambda expression () -> System.out.println(“Executing myRunnable”) defines a block of code that prints “Executing myRunnable” to the console.
An instance of the MyRunnable interface is created using the lambda expression and assigned to the variable myRunnable.
The run() method of the functional interface is called using the myRunnable.run() statement. As a result, the code defined in the lambda expression is executed, and “Executing myRunnable” is printed to the console.

Using Functional Interfaces with Lambda Expressions

Lambda expressions are concise blocks of code that implement functional interfaces. They were introduced in Java 8 and are a powerful way to write code that is both concise and expressive. Lambda expressions are just like methods in java but they make the code short, clean, and readable. They are also called anonymous functions. Thus, they are used more frequently with functional interfaces in Java.

Here is an example of how to use a lambda expression to implement the Greeter interface:

 
				
					@FunctionalInterface
public interface Greeter {
  void greet(String name);

  default String getGreetingMessage(String name) {
    return "Hello, " + name + "!";
  }
}

public class Main {
     public static void main(String [] args) {
        Greeter greeter = (name) -> System.out.println("Hello, " + name + "!");
        greeter.greet("Alice"); // Hello, Alice!
    }
}

				
			

Default and Static Methods in Functional Interfaces

In addition to abstract methods, functional interfaces can also have default and static methods. Default methods provide implementations for certain methods, while static methods are associated with the interface itself, rather than with instances of the interface.

Here is an example of a functional interface with a default and static methods:

				
					@FunctionalInterface
interface MyMathOperation {
    // Static method to find the absolute value
    static int absoluteValue(int a) {
        return (a < 0) ? -a : a;
    }
    
    // Abstract method
    int operate(int a, int b);

    // Default method to square a number
    default int square(int a) {
        return a * a;
    }
}

public class ExampleWithDefaultAndStaticMethods {
    public static void main(String[] args) {
        // Using lambda expression to implement the operate method
        MyMathOperation addition = (a, b) -> a + b;

        // Using the abstract method
        System.out.println("Addition: " + addition.operate(5, 3)); // Addition: 8

        // Using the default method
        System.out.println("Square: " + addition.square(4));       // Square: 16

        // Using the static method
        System.out.println("Absolute Value: " + MyMathOperation.absoluteValue(-7)); //Absolute Value: 7
    }
}
				
			

Built-in Java Functional Interfaces

Java has pre-defined or built-in functional interfaces for commonly occurring cases. Many interfaces are converted into functional interfaces using @FunctionalInterface Annotation. A few of these interfaces are as follows-

  • Runnable – It contains only the run() method. It is used to write applications that can run on a separate thread.
  • Comparable – This interface contains only compareTo() method. The objects of the class that implements the Comparable interface can be compared the objects of the class that implements the Comparable interface can be compared and sorted.
  • ActionListener – It contains only actionPerformed() method. It is responsible for handling all the action events like a mouse click on a component.
  • Callable – It only contains the call() method. This method is used to monitor the progress of a function being executed by a thread.

Types of Functional interfaces

Functional interfaces in Java are mainly of four types:

  1. Consumer
  2. Predicate
  3. Function
  4. Supplier

Function

The function type functional interface receives a single argument, processes it, and returns a value. One of the applications of this type of functional interface is taking the key from the user as input and searching for the value in the map for the given key.

Syntax:

				
					@FunctionalInterface
public interface Function<T, R>{
    R apply(T t);
}

				
			

Explanation: @FunctionalInterface is an annotation to ensure our interface is functional only. The function takes two generic types. The first one T is the type it takes a parameter and R is the return type of the abstract method. apply() is the abstract method of the Function.

Example:

				
					import java.util.function.Function;

public class FunctionExample {
    public static void main(String[] args) {
        // Function to add 1 to the input integer
        Function<Integer, Integer> addOne = x -> x + 1;

        // Applying the function
        int result = addOne.apply(5);
        System.out.println("Result: " + result);  // Output: Result: 6
    }
}

				
			

Bi-Function

Bi-function is just like a function except it takes two arguments. Two arguments are must in Bi-function. Just like a function it also returns a value.

UnaryOperator and BinaryOperator Interfaces

Unary and Binary are two functional interfaces. Unary extends Function and Binary extends Bi-function functional interface. Thus, Unary excepts one argument and returns a value, Binary takes two arguments but they must be of the same type. Also, the return value must be of the same type as the argument(s) for Unary and Binary operators.

  1. UnaryOperator Interface

Syntax and Example:

				
					@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T>{
   // add methods here
}

				
			
				
					import java.util.function.UnaryOperator;

public class UnaryOperatorExample {
    public static void main(String[] args) {
        // UnaryOperator to square an integer
        UnaryOperator<Integer> square = x -> x * x;

        // Applying the UnaryOperator
        int result = square.apply(5);
        System.out.println("Square: " + result);  // Output: Square: 25
    }
}

				
			
  1. BinaryOperator Interface
Syntax and Example:
				
					@FunctionalInterface 
public interface BinaryOperator<T> extends BiFunction<T, T, T>
    { // add methods here }
				
			
				
					import java.util.function.Function;
import java.util.function.BinaryOperator;
  
public class Main{
    public static void main(String args[]){
    // Given two numbers, bitwise logical operator & (and) will perform the logical operation between given numbers.
        BinaryOperator<Integer> and = (a,b) -> a & b;
        
        System.out.println(and.apply(12, 4)); // 4
    }
}

				
			

Supplier

The Supplier interface represents a function that takes no arguments and returns a result. It defines a single abstract method, get(), which takes no arguments and returns a value. This interface is commonly used to create instances of objects.

Syntax and Example:

				
					@FunctionalInterface
public interface Supplier<T>{
    T get();
}

				
			
				
					import java.util.function.Supplier;

public class SupplierExample {
    public static void main(String[] args) {
        // Supplier to generate a random number
        Supplier<Double> randomSupplier = () -> Math.random();

        // Getting the result from the Supplier
        double randomNumber = randomSupplier.get();
        System.out.println("Random Number: " + randomNumber);
    }
}

				
			

Consumer

The Consumer interface represents a function that takes a single argument and returns no result. It defines a single abstract method, accept(), which takes a single argument and returns no value. This interface is commonly used to process elements of a collection.

Syntax:

				
					public interface Consumer<T> {
    void accept(T t);
}
				
			

Example:

				
					import java.util.function.Consumer;

public class ConsumerExample {
    public static void main(String[] args) {
        // Consumer to print the given string
        Consumer<String> printString = s -> System.out.println(s);

        // Applying the consumer
        printString.accept("Hello, World!");  // Output: Hello, World!
    }
}

				
			

BiConsumer

The BiConsumer interface represents a function that takes two arguments and returns no result. It defines a single abstract method, accept(), which takes two arguments and returns no value. This interface is commonly used to process pairs of elements.

Syntax:

				
					public interface BiConsumer<T, U> {
    void accept(T t, U u);
}

				
			

Example:

				
					import java.util.function.BiConsumer;

public class BiConsumerExample {
    public static void main(String[] args) {
        // BiConsumer to print the two input integers
        BiConsumer<Integer, Integer> printSum = (a, b) -> System.out.println("Sum: " + (a + b));

        // Applying the BiConsumer
        printSum.accept(3, 5);  // Output: Sum: 8
    }
}

				
			

Predicate

The predicate functional interface in Java takes a single argument and returns a boolean value. It is usually used in filtering values from the collection. It is also a specialization of a Function that takes a gentrified argument or a single argument and returns a boolean.

The predicate is much like a metal detecting machine. It tests each object. If the object is detected to be metal, it sends signals in the form of sound or light, i.e. returns true. If the object is not metal, it simply checks another object or returns false.

Predicate interface in Java has 3 default methods and(),negate(), and or(). It has one static method isEqual() and a abstract method test().

Syntax and Example:

				
					// Syntax
@FunctionalInterface
interface MyPredicate<T> {
    boolean test(T t);
}

// Example
public class PredicateExample {
    public static void main(String[] args) {
        // Using lambda expression to implement the test method for checking even numbers
        MyPredicate<Integer> isEven = n -> n % 2 == 0;

        // Testing if a number is even
        boolean result = isEven.test(10);
        System.out.println("Is the number even? " + result);
    }
}

				
			

Conclusion

Java 8’s functional interfaces, coupled with lambda expressions, brought a paradigm shift to the language by facilitating functional programming. Understanding these functional interfaces and incorporating them into your code can lead to more concise, readable, and expressive Java programs. Whether you’re working with concurrent tasks, sorting collections, or applying transformations, functional interfaces play a crucial role in enhancing the power and elegance of your Java code.