Java Functional Interfaces

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

  1. Built-In Functional Interfaces
    • Predicate
    • Function
    • Consumer
    • Supplier
    • UnaryOperator
    • BinaryOperator
    • BiPredicate
    • BiFunction
    • BiConsumer
  2. Creating Custom Functional Interfaces
  3. Using Lambda Expressions with Functional Interfaces
  4. 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.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top