Functional Interfaces in Java

Functional Interface(FI) refers to a special kind of interface, it's an interface with only one abstract method, that is, FI can have many other implemented methods, but only one abstract method.

package com.sosv.functional.generic;

public interface GenericInterface {

  String numberToText(String lang, Long number);

  default long countDigits(Long number) {
    return (long)(Math.log10(number) + 1);
  }
}

We can annotate with @FunctionalInterface an interface to validate that interface has only one abstract method.

@FunctionalInterface
public interface GenericInterface {

  String numberToText(String lang, Long number);
  default long countDigits(Long number) {
    return (long)(Math.log10(number) + 1);
  }

}

Before FI, if you want to create an instance of an interface, you can create a new class that implements the interface or create an anonymous class.

For example:

//Create new Class
public class GenericClass implements GenericInterface {
    @Override
    public String numberToText(String lang, Long number) {
        return "";
    }
}

var generic = new GenericClass();

//Annonymous class
var generic = new GenericInterface() {
    @Override
    public String numberToText(String lang, Long number) {
        return "";
    }
};

Lambda Expressions(LE)

Since java SE 8, In addition to FI, we have lambda expressions. For me, LE is an implementation of a FI. We can reduce the above verbose code into cleaner code, easy to read.

GenericInterface generic = (String lang, Long number) -> "";

The basic notation for a lambda expression is:

(<parameters>) -><arrow operator> <body>

parameters: It can be 0, 1, or N parameters, you can specify the type of all parameters or neither parameter. Ex: (String lang, Long number) or (lang, number)

arrow operator: It's a "-", followed by ">". Ex: ->

body: It can have {} or not. depends on the implementation of the lambda expression.

If body doesn't have {}, return keyword is implicit;

{} it's mandatory when the abstract method doesn't have a return type, that is, void.

Ex: "" or {return "";}

Kinds of FI

We have basically 4 types of FI, and all of these FI are in java.util.function package.

FIInputReturn
Predicate(T param)Boolean
Function(T param)(R response)
Supplierno param(R response)
Consumer(T param)no response

T, R: Generic.

//Predicate
Predicate<String> hasNumber = (text) ->  text.matches(".*\\d+.*");
//Function
Function<Integer, Integer> square = (num1) -> num1 * num1;
//Supplier
Supplier<Double> random = () -> Math.random();
//Consumer
Consumer<String> logger = (text) -> System.out.println(text);

Operations of FI

//Predicate
Predicate<String> hasNumber = (text) ->  text.matches(".*\\d+.*");
Predicate<String> isLargeText = (text) ->  text.length() > 500;
hasNumber.negate() // or (text) ->  !text.matches(".*\\d+.*");
hasNumber.and(isLargeText) // or (text) -> text.matches(".*\\d+.*") && text.length() > 500
hasNumber.or(isLargeText)  // or (text) -> text.matches(".*\\d+.*") || text.length() > 500
hasNumber.test("Hello") //To get result of predicate


//Function
Function<Integer, Integer> square = (num1) -> num1 * num1;
Function<Integer, Integer> triple = (num1) -> num1 * 3;
square.andThen(triple) // or num*num*3
triple.andThen(square) // or (3*num)*(3*num)
triple.apply(2) // return 6

//Supplier
Supplier<Double> random = () -> Math.random();
random.get() // to get value

//Consumer
Consumer<String> logger = (text) -> System.out.println(text);
Consumer<String> greetings = (name) -> System.out.println("Hello " + name);
logger.andThen(greetings) // or print text and "Hello" + text
logger.accept("Hashnode") // to execute consumer

I hope you enjoy this summary of FI and LE.