Introduction
Functional interfaces in Java are interfaces that have exactly one abstract method. They can have multiple default or static methods but only one abstract method. These interfaces are the foundation of lambda expressions and method references in Java. The concept of functional interfaces is central to Java’s implementation of functional programming principles.
Key Points:
- Single Abstract Method (SAM): Functional interfaces contain only one abstract method.
- Lambda Expressions: Functional interfaces can be implemented using lambda expressions.
- Built-In Functional Interfaces: Java provides several built-in functional interfaces in the
java.util.function
package. - Custom Functional Interfaces: You can also create your own functional interfaces.
Table of Contents
- Built-In Functional Interfaces
- Predicate
- Function
- Consumer
- Supplier
- UnaryOperator
- BinaryOperator
- BiPredicate
- BiFunction
- BiConsumer
- Creating Custom Functional Interfaces
- Using Lambda Expressions with Functional Interfaces
- Conclusion
1. Built-In Functional Interfaces
Predicate
The Predicate
interface represents a predicate (boolean-valued function) of one argument. It is commonly used for filtering collections.
Abstract Method:
boolean test(T t);
Example:
import java.util.function.Predicate;
public class PredicateExample {
public static void main(String[] args) {
Predicate<String> isLongerThan5 = s -> s.length() > 5;
System.out.println(isLongerThan5.test("Hello")); // false
System.out.println(isLongerThan5.test("Hello, World!")); // true
}
}
Function
The Function
interface represents a function that accepts one argument and produces a result.
Abstract Method:
R apply(T t);
Example:
import java.util.function.Function;
public class FunctionExample {
public static void main(String[] args) {
Function<String, Integer> lengthFunction = s -> s.length();
System.out.println(lengthFunction.apply("Hello")); // 5
}
}
Consumer
The Consumer
interface represents an operation that accepts a single input argument and returns no result.
Abstract Method:
void accept(T t);
Example:
import java.util.function.Consumer;
public class ConsumerExample {
public static void main(String[] args) {
Consumer<String> printConsumer = s -> System.out.println(s);
printConsumer.accept("Hello, World!"); // Hello, World!
}
}
Supplier
The Supplier
interface represents a supplier of results. It does not accept any arguments.
Abstract Method:
T get();
Example:
import java.util.function.Supplier;
public class SupplierExample {
public static void main(String[] args) {
Supplier<String> helloSupplier = () -> "Hello, World!";
System.out.println(helloSupplier.get()); // Hello, World!
}
}
UnaryOperator
The UnaryOperator
interface represents an operation on a single operand that produces a result of the same type as its operand.
Abstract Method:
T apply(T t);
Example:
import java.util.function.UnaryOperator;
public class UnaryOperatorExample {
public static void main(String[] args) {
UnaryOperator<String> toUpperCase = s -> s.toUpperCase();
System.out.println(toUpperCase.apply("hello")); // HELLO
}
}
BinaryOperator
The BinaryOperator
interface represents an operation upon two operands of the same type, producing a result of the same type as the operands.
Abstract Method:
T apply(T t1, T t2);
Example:
import java.util.function.BinaryOperator;
public class BinaryOperatorExample {
public static void main(String[] args) {
BinaryOperator<Integer> sum = (a, b) -> a + b;
System.out.println(sum.apply(2, 3)); // 5
}
}
BiPredicate
The BiPredicate
interface represents a predicate (boolean-valued function) of two arguments.
Abstract Method:
boolean test(T t, U u);
Example:
import java.util.function.BiPredicate;
public class BiPredicateExample {
public static void main(String[] args) {
BiPredicate<String, Integer> isLongerThan = (s, i) -> s.length() > i;
System.out.println(isLongerThan.test("Hello", 5)); // false
System.out.println(isLongerThan.test("Hello, World!", 5)); // true
}
}
BiFunction
The BiFunction
interface represents a function that accepts two arguments and produces a result.
Abstract Method:
R apply(T t, U u);
Example:
import java.util.function.BiFunction;
public class BiFunctionExample {
public static void main(String[] args) {
BiFunction<String, String, String> concat = (s1, s2) -> s1 + s2;
System.out.println(concat.apply("Hello, ", "World!")); // Hello, World!
}
}
BiConsumer
The BiConsumer
interface represents an operation that accepts two input arguments and returns no result.
Abstract Method:
void accept(T t, U u);
Example:
import java.util.function.BiConsumer;
public class BiConsumerExample {
public static void main(String[] args) {
BiConsumer<String, Integer> print = (s, i) -> System.out.println(s + i);
print.accept("Age: ", 30); // Age: 30
}
}
2. Creating Custom Functional Interfaces
You can create your own functional interfaces by defining an interface with a single abstract method and annotating it with @FunctionalInterface
.
Example:
@FunctionalInterface
interface MyCustomFunctionalInterface {
void myMethod(String message);
}
public class CustomFunctionalInterfaceExample {
public static void main(String[] args) {
MyCustomFunctionalInterface custom = message -> System.out.println(message);
custom.myMethod("Hello, Custom Functional Interface!"); // Hello, Custom Functional Interface!
}
}
Explanation:
In the example above, MyCustomFunctionalInterface
is a custom functional interface with a single abstract method myMethod
. The lambda expression message -> System.out.println(message)
is an implementation of this interface, printing the message to the console.
3. Using Lambda Expressions with Functional Interfaces
Lambda expressions provide a way to create anonymous methods that can be assigned to variables, passed as arguments, or returned from methods. They are particularly useful for implementing functional interfaces concisely.
Example:
import java.util.function.Predicate;
public class LambdaExample {
public static void main(String[] args) {
Predicate<String> isEmpty = s -> s.isEmpty();
System.out.println(isEmpty.test("")); // true
System.out.println(isEmpty.test("Hello")); // false
}
}
Explanation:
In the example above, the Predicate
functional interface is implemented using a lambda expression s -> s.isEmpty()
. This lambda expression checks if a string is empty and returns true
or false
.
4. Conclusion
Functional interfaces in Java provide a powerful way to utilize lambda expressions and functional programming principles. Java offers several built-in functional interfaces, such as Predicate
, Function
, Consumer
, Supplier
, UnaryOperator
, BinaryOperator
, BiPredicate
, BiFunction
, and BiConsumer
. Additionally, you can create your own custom functional interfaces to suit your specific needs.
By understanding and effectively using functional interfaces and lambda expressions, you can write more expressive, concise, and maintainable Java code.