Functional Interfaces in Java 8

Functional Interfaces in Java 8

Introduction

Java 8 introduced a new concept to the Java language: functional interfaces. A functional interface is an interface that contains only one abstract method. It can have any number of default methods, but only one abstract method. These interfaces are used to create lambda expressions, which are a way of writing code in a more concise and elegant way.

For example Consumer interface has only one abstract method accept.

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

Functional interfaces are used primarily for creating lambda expressions. Lambda expressions are similar to anonymous inner classes, but they are much more concise and easier to read. Lambda expressions are written using the arrow operator (->) and can be used to represent any method. For example, a lambda expression to print out a string could look like this:

(String s) -> System.out.println(s);

This code internally creates implementation of Consumer functional interface and implements its accept method.

Functional interfaces are also useful for creating code that is easier to test. Since the interface only contains one abstract method, it can be mocked easily and the tests can be written in a more concise way.

Functional interfaces are also helpful for writing code that is easier to read and understand. They provide an easy way to create concise, self-contained functions that can be used anywhere in the code. This makes code more modular and easier to maintain.

Functional interfaces are an important part of the Java 8 language and are used extensively in modern Java code. They provide an easy way to write concise, testable, and readable code, and should be used whenever possible.

There are multiple types of functional interfaces already provided in Java. Few of the examples are below:

Predicate

Predicate<T> - evaluates a single argument and returns a boolean result.

BiPredicate<T, U> - evaluates two arguments and returns a boolean result.

public class _Predicate {
  public static void main(String[] args) {
    System.out.println("Number is greater than 10 ? " + greaterThan10Predicate.test(15));

    System.out.println("Number is positive ? " + signPredicate.test(10, true));
    System.out.println("Number is positive ? " + signPredicate.test(10, false));

    System.out.println("Number is even ? " + evenPredicate.test(10));
    System.out.println("Number is even ? " + evenPredicate.test(13));

    // clubbing Predicates using and function
    System.out.println("Number is even and greater than 10? " + evenPredicate.and(greaterThan10Predicate).test(8));
    System.out.println("Number is even and greater than 10? " + evenPredicate.and(greaterThan10Predicate).test(15));
    System.out.println("Number is even and greater than 10? " + evenPredicate.and(greaterThan10Predicate).test(12));

    // clubbing Predicates using or function
    System.out.println("Number is even or greater than 10? " + evenPredicate.or(greaterThan10Predicate).test(10));
    System.out.println("Number is even or greater than 10? " + evenPredicate.or(greaterThan10Predicate).test(-11));
  }

  // using implementation of Predicate interface
  static Predicate<Integer> greaterThan10Predicate = new GreaterThan10();

  // using Predicate lambda
  static Predicate<Integer> evenPredicate = (number) -> number % 2 == 0;

  // using BiPredicate lambda
  static BiPredicate<Integer, Boolean> signPredicate = (number, isPositive) -> {
    if (isPositive) return number > 0;
    else return number <= 0;
  };
}

class GreaterThan10 implements Predicate<Integer> {
  @Override
  public boolean test(Integer number) {
    return number > 10;
  }
}

Function

Function<T, R> - accepts a single argument and produces a result.

BiFunction<T, U, R> - accepts two arguments and produces a result.

public class _Function {
  public static void main(String[] args) {
    System.out.println("Increment number: " + addByOne.apply(2));
    System.out.println("Multiply number by 10: " + multiplyByTen.apply(5));
    System.out.println("Increment number and multiply by 10: " + incrementAnd10Times.apply(2));
    System.out.println("Increment first argument and then multiply by second argument: " + incrementFirstAndMultiplySecond.apply(2, 10));

  }

  // using implementation of Function interface
  static Function<Integer, Integer> addByOne = new AddByOne();

  // Using lambda
  static Function<Integer, Integer> multiplyByTen = number -> number * 10;

  // Chaining Functions using andThen
  static Function<Integer, Integer> incrementAnd10Times = addByOne.andThen(multiplyByTen);

  // Using BiFunction
  static BiFunction<Integer, Integer, Integer>
      incrementFirstAndMultiplySecond = (firstArg, secondArg) -> (firstArg + 1) * secondArg;
}

class AddByOne implements Function<Integer, Integer> {
  @Override
  public Integer apply(Integer number) {
    return number + 1;
  }
}

Consumer

Consumer<T> - accepts a single argument, but does not return a result.

BiConsumer<T, U> - accepts two arguments, but does not return a result.

public class _Consumer {

  public static void main(String[] args) {
    helloConsumer.accept("Sandeep");
    welcomeConsumer.accept("MyCompany");
    greetConsumerBiFunction.accept("Platform", 1234);
  }

  // creating consumer with Functional interface implementation
  static Consumer<String> helloConsumer = new HelloConsumer();

  // creating consumer with lambda
  static Consumer<String> welcomeConsumer = (place) -> System.out.println("Welcome to " + place);

  // creating BiConsumer with lambda
  static BiConsumer<String, Integer> greetConsumerBiFunction = (teamName, empId) -> System.out.println("You have joined " + teamName + " team with id " + empId);
}


class HelloConsumer implements Consumer<String> {
  @Override
  public void accept(String name) {
    System.out.println("Hello " + name + ".");
  }
}

Supplier

Supplier<T> - does not accept any argument, but returns a result.

public class _Supplier {
  public static void main(String[] args) {
    System.out.println("Fetch DB url: " + dbConnectionUrlSupplier.get());
    System.out.println("Generate random number: " + randomNumberSupplier.get());
  }

  // using implementation of Supplier interface
  static Supplier<Double> randomNumberSupplier = new RandomNumberSupplier();
  // using lambda
  static Supplier<String> dbConnectionUrlSupplier = () -> "mysql://localhost:3600";
}

class RandomNumberSupplier implements Supplier<Double> {
  @Override
  public Double get() {
    return Math.random() * 100;
  }
}

Conclusion

In conclusion, functional interfaces are an important part of the Java 8 language and are used to create code that is easier to read, test, and maintain. There are several different functional interfaces, each of which is designed to serve a specific purpose. By using functional interfaces, developers can write code that is more concise and easier to understand, making it easier to develop complex applications.