Functional Interface in Java
In our last article, we discussed the Reflection API in Java. In this article, we will discuss the Functional interface in Java and @FunctionalInterface Annotation in Java. Within@FunctionalInterface Annotation, we will discuss the Java.util.function package with examples.
We will also discuss Lambda Expression in the Java programming language.
Functional Interface in Java
A functional interface is an interface in which there is only one abstract method. A functional interface has only one functionality to exhibit. From Java 8 onwards, we can use lambda expressions to represent the instance of a functional interface.
There can be any number of default and static methods in a functional interface that have an implementation. Some of the examples of functional interfaces in Java are Runnable, ActionListener, Comparable interfaces.
We had to use anonymous inner class objects to implement functional interfaces before Java 8.
Rules of defining a Functional Interface
The functional interface must have only one abstract method. Along with the one abstract method, they can have any number of default and static methods.
Examples of Functional Interface
The example of functional interface example in Java is:
public interface MyFunctionalInterface { public void mymethod(); }
The above example shows a functional interface because it only contains a single method that has no implementation.
Following code snippet is another example of a Java functional interface that has an implementation of some methods:
public interface MyFunctionalInterface { public void myMethod(); public default void printText(String text) { System.out.println(text); } public static void printText(String text, PrintWriter writer) throws IOException { writer.write(text); } }
The above interface is a functional interface because it only contains a single non-implemented method.
Implementation of Functional Interfaces by a Lambda Expression in Java
Lambda Expressions in Java are the way through which we can visualize functional programming in the object-oriented world. Objects are the basic building block of Java programming language and we can never use a function without an object.
Therefore, Java provides support for using lambda expressions only with functional interfaces. We can implement a Java functional interface using Java Lambda Expression.
Examples of Java Lambda Expressions
Below are some code snippets for lambda expressions with small comments explaining them.
() -> {} | No parameters; void result |
() -> 42 | No parameters, expression body |
() -> null | No parameters, expression body |
() -> { return 42; } | No parameters, block body with return |
() -> { System.gc(); } | No parameters, void block body |
(int x) -> x+1 | Single declared-type argument |
(int x) -> { return x+1; } | same as above |
(x) -> x+1 | Single inferred-type argument, same as below |
x -> x+1 | Parentheses optional for single inferred-type case |
(String s) -> s.length() | Single declared-type argument |
(Thread t) -> { t.start(); } | Single declared-type argument |
s -> s.length() | Single inferred-type argument |
t -> { t.start(); } | Single inferred-type argument |
(int x, int y) -> x+y | Multiple declared-type parameters |
(x,y) -> x+y | Multiple inferred-type parameters |
(x, final y) -> x+y | Illegal: can’t modify inferred-type parameters |
(x, int y) -> x+y | Illegal: can’t mix inferred and declared types |
Program to Implement Java Functional Interface using Lambda Expressions
//Java program to implement the functional interface using lambda expressions public class Example { public static void main(String args[]) { //lambda expression to create the object new Thread(() - >{ System.out.println("Created a new thread"); }).start(); } }
Output:
Why do we need Lambda Expression?
We need to use Lambda Expressions due to following reasons:
1. Reduced Lines of Code
Reduced lines of code is one of the clear benefits of using a lambda expression. We can create instances of a functional interface using lambda expression rather than using an anonymous class.
2. Sequential and Parallel Execution Support
Another advantage of using lambda expression is that we can get a Stream API sequential and parallel execution support.
3. Passing Behaviors into methods
We can use lambda expressions to pass the behavior of a method. Let’s understand this with a simple example. Suppose, we have to write a method to sum the numbers in a list if they match the given condition.
We can use the Predicate interface and write a method like below.
public static int sumWithCondition(List < Integer > numbers, Predicate < Integer > predicate) { return numbers.parallelStream().filter(predicate).mapToInt(i - >i).sum(); }
For example:
//sum of all numbers sumWithCondition(numbers, n - >true) //sum of all even numbers sumWithCondition(numbers, i - >i % 2 == 0) //sum of all numbers greater than 5 sumWithCondition(numbers, i - >i > 5)
4. Higher Efficiency with Laziness
Lambda expressions provide a lazy evaluation with higher efficiency. For example, suppose, we have to write a method to find out the maximum odd number in the range 4 to 15 and return the square of it. Using Lambda expression, we can write the code like this:
public static int findSquareOfMaxOdd(List < Integer > numbers) { return numbers.stream().filter(NumberTest::isOdd) //Predicate is functional interface and .filter(NumberTest::isGreaterThan4) // we are using lambdas to initialize it .filter(NumberTest::isLessThan15) // rather than anonymous inner classes .max(Comparator.naturalOrder()).map(i - >i * i).get(); } public static boolean isOdd(int i) { return i % 2 != 0; } public static boolean isGreaterThan4(int i) { return i > 4; } public static boolean isLessThan15(int i) { return i < 15; }
@FunctionalInterface Annotation
We can use the @FunctionalInterface annotation to ensure that there is not more than one abstract method in a functional interface.
In case if there is more than one abstract method in the functional interface, the compiler gives an ‘Unexpected @FunctionalInterface annotation’ message. However, there is no compulsion to use this annotation.
Code to understand @FuctionalInterface Annotation in Java:
//Program to implement a user-defined functional interface using lambda expressions @FunctionalInterface interface Square { int calculateSquare(int x); } public class Test { public static void main(String args[]) { int num = 10; //lambda expression to define the calculate method Square sq = (int x) - >x * x; int answer = sq.calculateSquare(num); System.out.println("The square of the number is: " + answer); } }
Output:
Primitive Function Specializations
We know that primitive types cannot be a generic type argument. Therefore, there are versions of the Functional interface for some primitive types such as double, int, long, and their combinations in return types and argument:
- IntFunction, LongFunction, DoubleFunction: They take arguments of the specified type, and their return type is parameterized.
- ToIntFunction, ToLongFunction, ToDoubleFunction: They take parameterized arguments and the return type is of specified type.
- DoubleToIntFunction, DoubleToLongFunction, IntToDoubleFunction, IntToLongFunction, LongToIntFunction, LongToDoubleFunction: These have both argument and return type as primitive types, as specified by their names.
Two-Arity Function Specializations
We have to use additional interfaces to define lambdas with two arguments. They contain the “Bi” keyword in their names, for example, BiFunction, ToDoubleBiFunction, ToIntBiFunction, and ToLongBiFunction.
One of the examples of using this interface in the standard API is in the Map.replaceAll method. This method allows replacing all values in a map with some calculated value.
Let us see an implementation of a BiFunction. It receives a key and an old value to calculate a new value for the income and returns the value.
Map < String,Integer > income = new HashMap < >(); income.put("Sam", 70000); income.put("Tina", 40000); income.put("Lisa", 50000); income.replaceAll((name, oldValue) - >name.equals("Tina") ? oldValue: oldValue + 20000);
Legacy Functional Interfaces in Java
Java 8 did not introduce all the functional interfaces. There were many functional interfaces that came from the previous versions of Java and can be used as lambdas.
A significant example is the Runnable and Callable interfaces used in concurrency APIs. Java 8 also marks these interfaces with a @FunctionalInterface annotation.
Built-in Functional Interfaces in Java
Java provides a set of functional interfaces designed for common use cases. So there is no need to create your own functional interfaces for every little use case. In this section, we will learn some built-in functional interfaces in Java which are present in the java.util.function package.
The java.util.function package
The java.util.function package in Java 8 has many built-in functional interfaces like:
1. Function
The Java Function interface or java.util.function.Function interface is one of the most important functional interfaces in Java. The Function interface represents a method that takes a single parameter and returns a single value.
The definition of Function interface looks like:
public interface Function < T,R > { public < R > apply(T parameter); }
2. Predicate
The java.util.function.Predicate is a functional interface that represents a simple function. This function takes a single value as a parameter and returns either true or false.
The definition of Predicate functional interface looks like:
public interface Predicate { boolean test(T t); }
Code to implement the Predicate Interface in Java:
// A program to demonstrate the use of predicate interface import java.util. * ; import java.util.function.Predicate; class Test { public static void main(String args[]) { //create a list of strings List < String > names = Arrays.asList("Tech", "Vidvan", "Java", "Tutorials", "TV2"); /*declare the predicate type as a string and use a lambda expression to create object */ Predicate < String > p = (s) - >s.startsWith("T"); // Iterate through the list for (String st: names) { // call the test method if (p.test(st)) System.out.println(st); } } }
Output:
Tutorials
TV2
3. Unary Operator
The java.util.function.UnaryOperation interface in Java represents an operation. This operation takes a single parameter and returns a parameter of the same type.
Below is the example of a Java UnaryOperator implementation:
UnaryOperator < Person > unaryOperator = (person) - >{ person.name = "New Name"; return person; };
4. Binary Operator
Java Binary Operator interface represents an operation that takes two parameters and returns a single value. Both parameters and the return type of the function must be of the same type.
The Java BinaryOperator interface is useful when there is a need to implement functions like sum, subtract, divide, multiply, etc., that have two elements of the same type, and return a third element of the same type.
Below is an example of implementation of the BinaryOperator interface:
BinaryOperator < MyValue > binaryOperator = (value1, value2) - >{ value1.add(value2); return value1; };
5. Supplier
The Supplier interface of Java is a functional interface that represents a function that supplies a value of some sort. This interface can also be thought of as a factory interface.
Below is an example of implementation of the Java Supplier interface:
Supplier < Integer > supplier = () - >new Integer((int)(Math.random() * 1000D));
6. Consumer
The Consumer interface of java.util/function package is a functional interface that represents a function that takes a value without returning any value. The implementation of Java Consumer interface can be used in printing out some values or writing a value to a file, or over the network, etc.
Below is an example of implementation of the Java Consumer interface:
Consumer < Integer > consumer = (value) - >System.out.println(value);
Functional interface example: using anonymous inner class vs using a lambda expression
We have been using functional interfaces even before the introduction of Java 8. We used them by creating anonymous inner classes using functional interfaces. The functional interfaces such as Runnable, ActionListener, Comparator, etc. have a single abstract method.
Let us see an example of ActionListener. We will compare how we used ActionListener using Anonymous inner class before Java 8 and how we can implement it using lambda expressions.
ActionListener Example Using an anonymous inner class: Before Java 8:
import javax.swing. * ; import java.awt. * ; import java.awt.event. * ; class Example extends JFrame { JButton button; public Example() { setTitle("Button Action Example without Lambda Expression"); setSize(400, 300); setVisible(true); setLayout(new FlowLayout()); setDefaultCloseOperation(EXIT_ON_CLOSE); button = new JButton("Button"); button.setBounds(100, 100, 90, 40); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.out.println("You clicked the button."); } }); add(button); } public static void main(String args[]) { new Example(); } }
ActionListener Example: Lambda Expression: From Java 8:
import javax.swing. * ; import java.awt. * ; class Example extends JFrame { JButton button; public Example() { setTitle("Button Action Example using Lambda Expression"); setSize(400, 300); setVisible(true); setLayout(new FlowLayout()); setDefaultCloseOperation(EXIT_ON_CLOSE); button = new JButton("Button"); button.setBounds(100, 100, 90, 40); //Lambda expression button.addActionListener(e - >System.out.println("You clicked the button.")); add(button); } public static void main(String args[]) { new Example(); } }
Output for both the programs is the same:
Important Points/Observations
1. There can be only one abstract method in a functional interface but there can be multiple default methods in it.
2. @FunctionalInterface annotation ensures that a functional interface cannot have more than one abstract method. The use of this annotation is not mandatory.
3. There are many built-in functional interfaces in the java.util.function package
Conclusion
Hence, in this Java Functional Interface tutorial, we learned about what functional interfaces are in Java with example.
We also discussed Java Lambda expressions and @FunctionalInterface Annotation in Java, in which we covered java.util.function Package. We also saw how Lambda expressions are beneficial for efficient coding.