Functional Interfaces in Java

Functional Interfaces in Java

Disclaimer: For the best experience, read this article in its original MD format, that includes embedded code snippets and references to code examples.

Functional Programming is a programming paradigm that decomposes a problem into a set of Functions.

Java 8 introduced some major features to support Functional Programming in Java allowing Data Immutability and enabling Functions as First-class citizens, introducing a new syntax, lambda expressions, to make Functions easier to write and read.

Also, Functional Interfaces were introduced to provide meaningful semantics to functions.

What Functional Interfaces are?

A Functional Interface in Java is an Interface with a Single Abstract Method (SAM). Java ensures the integrity of these interfaces at the compiler level with a single-method rule.

They are designed to work seamlessly with lambda expressions and method references, providing Provide semantic clarity by explicitly defining the intent of a function.

Custom Functional Interfaces

We can define our own Custom Functional Interfaces annotating our interface with @FunctionalInterface. This enables the compiler to enforce SAM rule so that we provide one and only one Abstract Method to be implemented.

Let's see an example of a custom functional interface

@FunctionalInterface
interface Greeting {
    void sayHello(String name);
}        

This functional interface can be implemented quite easily using a lambda expression:

Greeting greet = name -> System.out.println("Hello, " + name);        

Java native Functional Interfaces

Java provides several built-in functional interfaces in the java.util.function package for common use cases in Functional Programming to best categorize and reuse functions when decomposing problems:

Predicates

Predicate<T> represents a Condition.

boolean test(T t)        

They are mainly used for data filtering and segmentation, which can be handy in data intelligence to identify patterns (risk analysis, fraud detection).

@Test
public void testPredicates() {
    Predicate<String> isLongString = s -> s.length() > 5;
    Predicate<String> startsWithF = s -> s.startsWith("f") || s.startsWith("F");

    assertFalse(isLongString.test("hello"));
    assertTrue(isLongString.test("functional"));
    assertTrue(startsWithF.test("functional"));
    // Predicates can be easily chained
    assertTrue((isLongString.and(startsWithF)).test("functional"));
}        

Consumer

A Consumer<T> is an operation that accepts one type T of data and produces no result.

void accept(T t)        

This is a good way to isolate side effects, and can be used for persisting data, auditing, metrics collection, sending notifications...

@Test
public void testConsumers() {
    Consumer<String> printMessage = s -> System.out.println("Logging: " + s);
    printMessage.accept("Example log message"); // Output: "Logging: Example log message"
}        

Supplier

A Supplier<T> is a function that takes no argument and produces a result of type T.

T get()        

They are mainly used to generate data for testing, configuration, default values...

@Test
public void testSuppliers() {
    Supplier<Double> randomValue = () -> Math.random();
    // Supplier<User> getRandomUser = User::random; // Example of a class User that would have one method random to generate a random user to be used in testing.
    System.out.println(randomValue.get());
}        

Function<T, R>

Function<T, R> interfaces are Functions that take a type T as input parameter and returns type R as result.

R apply(T t)        

They are typically used for transforming data from type T to type R, which can be handy for transforming data form one domain to another, like generating reports, or currency conversion, processing natural language...

@Test
public void testFunctions() {
    Function<Integer, String> intToString = i -> "Number: " + i;
    assertEquals("Number: 42", intToString.apply(42));
}        

Unitary Operator

UnaryOperator<T> is a Function where the input and the output are of the same type T. It is actually an specialization of Function<T, T>.

T apply(T t)        

Typically used to modify data in batches, such as updating statuses when an order has been shipped, apply taxes, data normalization.

prices.stream()
                .map(price -> price * 0.9)
                .collect(Collectors.toList());        

BiFunction

BiFunction<T, U, R> is a Function that takes two arguments and produces a result.

R apply(T t, U u)        

They are mainly used to combine data for conflict resolution, data enrichment or generating financial summaries.

@Test
public void testCompareNames() {
    // BiFunction<String, String, Boolean> twoUsersHaveSameName = String::equalsIgnoreCase;
    BiFunction<String, String, Boolean> twoUsersHaveSameName = (firstName, secondName) -> firstName.equalsIgnoreCase(secondName);

    assertFalse(twoUsersHaveSameName.apply("John", "Peter"));
    assertTrue(twoUsersHaveSameName.apply("John", "joHN"));
}        

BinaryOperator

BinaryOperator<T> is a specialization of BiFunction<T, T, T>.

T apply(T t1, T t2)        

An example can be the following

@Test
public void testComposeFullNames() {
    BinaryOperator<String> fullNameUsingBinaryOperator = (firstName, lastName) -> lastName + ", " + firstName;
    BiFunction<String, String, String> fullNameUsingBiFunction = (firstName, lastName) -> lastName + ", " + firstName;

    assertEquals("Doe, John", fullNameUsingBinaryOperator.apply("John", "Doe"));
    assertEquals("Doe, John", fullNameUsingBiFunction.apply("John", "Doe"));
}        

More native Functional Interfaces

There are other well known Interfaces in Java that has benefit from these changes, like

  • Runnable since Java 1, represents a task to be executed on a separate thread.

Runnable task = () -> System.out.println("Task is running..."); new Thread(task).start();         

- Comparator since Java 2, for comparing and sorting objects.

 Comparator<String> byLength = (s1, s2) -> Integer.compare(s1.length(), s2.length()); List<String> words = List.of("apple", "banana", "cherry"); words.sort(byLength);        

  • Callable since Java 5, is similar to Runnable but returns a value and can throw exceptions

Callable<String> task = () -> "Task completed!";
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
  Future<String> result = executor.submit(task);
  System.out.println(result.get());
} catch (Exception e) {
  e.printStackTrace();
} finally {
  executor.shutdown();
}        

  • ActionListener since Java 1, to handle events

button.addActionListener(e -> System.out.println("Button clicked!"));        


To view or add a comment, sign in

More articles by Jerónimo Calvo Sánchez

  • Practical Habits to Counter Automation Bias in Software Development

    "Even a broken watch is right twice a day." Throughout history, we have trusted our instruments without second thoughts…

  • Developer Experience

    What is Developer Experience? Developer experience (DevEx) refers to the overall experience and satisfaction that…

  • Getting started with Java Stream API

    Disclaimer: For the best experience, read this article in its original MD format, that includes embedded code snippets…

  • Mastering `Optional` in Java: Eliminate Nulls Enhancing Code Readability

    Disclaimer: For the best experience, read this article in its original MD format, that includes embedded code snippets…

  • Lambda Expressions in Java

    In Functional Programming, one of the consequences of Functions becoming First Class Citizens, is that they are used…

  • Functions as First-class citizens in Java

    Disclaimer: For the best experience, read this article in its original MD format, that includes embedded code snippets…

  • Data Immutability in Java

    Disclaimer: For the best experience, read this article in its original MD format, that includes embedded code snippets,…

  • Functional Programming in Java

    Functional Programming is a programming paradigm that decomposes a problem into a set of Functions, focusing on…

  • Introduction to Functional Programming

    What is Functional Programming In Computer Science, Functional Programming is a programming paradigm that decomposes a…

  • Menos es Más. Aumentar la productividad del sistema reduciendo

    Un proceso es una secuencia de actividades coordinadas para alcanzar un objetivo específico. Cada paso de nuestro…

    1 Comment

Others also viewed

Explore content categories