Java 8: Functional Interfaces
Functional Interface
- A Functional Interface is a type of interface that contains only one abstract method.
- It was introduced in Java 8 to support functional programming.
- Functional Interfaces are designed to fulfil a single purpose.
Defining a Functional Interface
There are multiple ways to define a Functional Interface:
- Creating an Interface with One Abstract Method
- Create an interface with one abstract method.
- Problem: Without additional safeguards, adding more than one abstract method is possible, violating the core definition of a Functional Interface.
- To prevent this issue, the
@FunctionalInterfaceannotation was introduced.
interface Person {
void hello();
}2. Using the @FunctionalInterface Annotation
- When you use the
@FunctionalInterfaceannotation, it enforces that the interface contains only one abstract method. - This helps ensure the correct use of the Functional Interface and prevents accidental additions of more abstract methods.
@FunctionalInterface
interface Person {
void hello();
static String helloFromString() {
return "Hello from interface String";
}
}3. Functional Interfaces with Return Values
- A Functional Interface can have an abstract method that returns a value based on requirements.
@FunctionalInterface
interface Person {
String hello();
static String helloFromString() {
return "Hello from interface String";
}
}4. Functional Interfaces with Parameters
- A Functional Interface can also have an abstract method that accepts parameters.
@FunctionalInterface
interface Person {
String hello(String str);
static String helloFromString() {
return "Hello from interface String";
}
}Functional Interfaces: Static, Default Methods, and Extensions
As the name suggests, a Functional Interface can only have one abstract method. However, it is allowed to have multiple static or default methods without violating its definition.
Example with Static and Default Methods
@FunctionalInterface
interface Person {
void hello(); // abstract method
static String helloFromString1() {
return "Hello from interface String 1";
}
static String helloFromString2() {
return "Hello from interface String 2";
}
default void defaultExample1() {
System.out.println("Default Example 1");
}
default void defaultExample2() {
System.out.println("Default Example 2");
}
}In the example above, the Person interface is still a valid Functional Interface because it has only one abstract method, hello(), even though it includes multiple static and default methods.
Extending a Functional Interface
A Functional Interface can be extended by another interface without violating its definition. However, the child interface must ensure it doesn’t add additional abstract methods if it wants to remain a Functional Interface.
Example: Extending a Functional Interface
@FunctionalInterface
interface Person {
void hello(); // abstract method
static String helloFromString1() {
return "Hello from interface String 1";
}
static String helloFromString2() {
return "Hello from interface String 2";
}
default void defaultExample1() {
System.out.println("Default Example 1");
}
default void defaultExample2() {
System.out.println("Default Example 2");
}
}interface Male extends Person {
void world(); // abstract method
default void defaultMale() {
System.out.println("Default Male Example");
}
}In this example, the Male interface extends the Person interface and introduces a new abstract method, world(). This makes Male a non-functional interface, while Person remains a Functional Interface.
Making the Child Interface a Functional Interface
To make the Male interface a Functional Interface, we can simply remove the additional abstract method (world()):
interface Male extends Person {
default void defaultMale() {
System.out.println("Default Male Example");
}
}Now, Male inherits the single abstract method hello() from Person and satisfies the criteria for being a Functional Interface.
Using a Functional Interface with Lambda Expressions
The real power of Functional Interfaces lies in their usage with Lambda Expressions. Lambda expressions allow you to define the abstract method’s implementation concisely without creating an explicit class.
Example: Functional Interface with a Lambda Expression
@FunctionalInterface
interface Person {
void hello();
}public class Main {
public static void main(String[] args) {
Person lam = () -> System.out.println("Lambda from Person");
lam.hello();
}
}Explanation:
- In the example above, no concrete implementation class for
Personwas created. - The
hello()the method is implemented directly using a Lambda Expression. - The output will be:
Lambda from Person
Lambda Expressions Overview
Lambda expressions provide a concise way to represent a method in a Functional Interface, enabling functional programming in Java. In the next chapter, we'll explore lambda expressions in detail.
Predefined Functional Interfaces in Java
Java provides several predefined functional interfaces under the java.util.function package. These interfaces can be directly used without the need to create custom functional interfaces.
Below are some commonly used predefined functional interfaces:
Get Nikhil Sharma’s stories in your inbox
Join Medium for free to get updates from this writer.
1. Function<T, R>
- Represents a function that takes one argument of type
Tand produces a result of typeR. - Example:
import java.util.function.Function;
public class Main {
public static void main(String[] args) {
Function<String, Integer> function = str -> str.length();
System.out.println(function.apply("hello"));
}
}- Output:
52. BiFunction<T, U, R>
- Represents a function that takes two arguments of types
TandUand produces a result of typeR. - Example:
import java.util.function.BiFunction;
public class Main {
public static void main(String[] args) {
BiFunction<String, String, String> function = (a, b) -> a + b;
System.out.println(function.apply("hello", "world"));
}
}- Output:
helloworld3. Consumer<T>
- Represents an operation that takes a single argument of type
Tand performs some operation without returning a result. - Example:
import java.util.function.Consumer;
public class Main {
public static void main(String[] args) {
Consumer<String> function = str -> System.out.println(str);
function.accept("hello");
}
}- Output:
hello4. BiConsumer<T, U>
- Represents an operation that takes two arguments of types
TandUand performs an operation without returning a result. - Example:
import java.util.function.BiConsumer;
public class Main {
public static void main(String[] args) {
BiConsumer<String, Integer> function = (str, number) -> System.out.println(String.format("The string is %s and the number is %d", str, number));
function.accept("hello", 3);
}
}
- Output:
The string is hello and the number is 35. Supplier<T>
- Represents a supplier of results. It does not take any arguments but produces a result of type
T. - Example:
import java.util.function.Supplier;
public class Main {
public static void main(String[] args) {
Supplier<String> function = () -> "I am a message from the Supplier";
System.out.println(function.get());
}
}- Output:
I am a message from the Supplier6. Predicate<T>
- Represents a predicate (a boolean-valued function) that takes one argument of type
T. - Example:
import java.util.function.Predicate;
public class Main {
public static void main(String[] args) {
Predicate<Integer> function = in -> in % 2 == 0;
System.out.println(function.test(6)); // true
System.out.println(function.test(7)); // false
}
}- EOutput:
true
false7. BiPredicate<T, U>
- Represents a predicate (a boolean-valued function) that takes two arguments of types
TandU. - Example:
import java.util.function.BiPredicate;
public class Main {
public static void main(String[] args) {
BiPredicate<Integer, Integer> function = (a, b) -> a % b == 0;
System.out.println(function.test(6, 3)); // true
System.out.println(function.test(7, 2)); // false
}
}- Output:
true
false8. UnaryOperator<T>
- Represents an operation on a single operand that produces a result of the same type as the operand.
- Example:
import java.util.function.UnaryOperator;
public class Main {
public static void main(String[] args) {
UnaryOperator<Integer> function = a -> a * a;
System.out.println(function.apply(2)); // 4
}
}- Output:
49. BinaryOperator<T>
- Represents an operation on two operands of the same type, producing a result of the same type.
- Example:
import java.util.function.BinaryOperator;
public class Main {
public static void main(String[] args)
{
BinaryOperator<Integer> function = (a, b) -> a * b;
System.out.println(function.apply(3, 2)); // 6
}
}- Output:
6Summary Chart of Functional Interfaces in java.util.function
Here is a comprehensive summary of all the functional interfaces in the java.util.function package:
+---------------------+-------------------------------------------------------------+------------------------+-------------+---------------------------------------------------+
| Interface | Purpose | Abstract Method | Utilization | Example Use Case |
+---------------------+-------------------------------------------------------------+------------------------+-------------+---------------------------------------------------+
| Function<T, R> | Transforms an input of type T into an output of type R. | R apply(T t) | apply() | Converting a string to its length. |
| BiFunction<T, U, R> | Transforms two inputs (T, U) into an output of type R. | R apply(T t, U u) | apply() | Concatenating two strings. |
| Consumer<T> | Performs an operation on a single input of type T. | void accept(T t) | accept() | Printing a message. |
| BiConsumer<T, U> | Performs an operation on two inputs (T, U). | void accept(T t, U u) | accept() | Printing a string with a number. |
| Supplier<T> | Supplies a value with no input. | T get() | get() | Providing a default value or lazy initialization. |
| Predicate<T> | Evaluates a boolean condition for a single input of type T. | boolean test(T t) | test() | Checking if a number is even. |
| BiPredicate<T, U> | Evaluates a boolean condition for two inputs (T, U). | boolean test(T t, U u) | test() | Checking divisibility of two numbers. |
| UnaryOperator<T> | Represents an operation on a single operand of type T. | T apply(T t) | apply() | Squaring a number. |
| BinaryOperator<T> | Represents an operation on two operands of the same type T. | T apply(T t1, T t2) | apply() | Multiplying two numbers. |
+---------------------+-------------------------------------------------------------+------------------------+-------------+---------------------------------------------------+Conclusion
The java.util.function package provides a rich set of predefined functional interfaces that simplify functional programming in Java. These interfaces enable concise and expressive code, especially when combined with Lambdas and the Stream API. By leveraging these functional interfaces, developers can perform common operations such as data transformation, filtering, and result generation with minimal effort.
Next Topic: Lambda Expressions
The next topic, Lambda Expressions, dives deeper into how these functional interfaces are implemented in Java. Lambda expressions allow us to write functional interface implementations compactly and elegantly, eliminating the need for verbose anonymous inner classes. We’ll explore the syntax, use cases, and how Lambdas enhance functional programming in Java.
Stay tuned for a hands-on exploration of Lambda expressions!








