Functional Programming in Java

Functional programming is a programming paradigm where programs are constructed by applying and composing functions. It is a declarative programming paradigm in which function definitions are trees of expressions that each return a value, rather than a sequence of imperative statements which change the state of the program

wikipedia

Java 8 introduced functional programming in the form of Lambda. Term Lambda comes from lambda calculus, which is used to describe computations.

Lambda

We can think of a lambda expression as an anonymous function that can be assigned to a variable and passed to a method, which accepts functional interface as an argument. Lambda expression doesn’t have a name, but it has a list of parameters, a body, and a return type.

(parameters) -> expression

A lambda expression can be used in the context of functional interface.

Functional Interface

A functional interface is an interface that specifies exactly one abstract method.

public interface Comparator<T> {                           
    int compare(T o1, T o2);
}
public interface Runnable {                                
    void run();
}

Lambda expressions let us provide the implementation of the abstract method of a functional interface directly inline and treat the whole expression as an instance of a functional interface.

Function Descriptor

We call the signature of the abstract method of the functional interface as a function descriptor. The function descriptor describes the signature of the lambda expression. For example, we can think of the function descriptor of Runnable as () -> void as it has one abstract method which accepts nothing and returns nothing (void).

Java Functional Interface

Predicate

The java.util.function.Predicate<T> interface defines an abstract method named test that accepts an object of generic type T and returns a boolean. This interface can be used to represent a boolean expression that uses an object of type T.

Function Descriptor : T -> boolean

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

Consumer

The java.util.function.Consumer<T> interface defines an abstract method named accept that takes an object of generic type T and returns no result (void). We can use this interface when we need to access an object of type T and perform some operations on it.

Function Descriptor : T -> void

Function

The java.util.function.Function<T, R> interface defines an abstract method named apply that takes an object of generic type T as input and returns an object of generic type R. We can use this interface when we need to define a lambda that maps information from an input object to output.

Function Descriptor : T -> R

Supplier

The java.util.function.Supplier<T> interface defines an abstract method named get that takes nothing and returns an object of type T.

Function Descriptor : () -> R

Primitive Specializations

Primitive interfaces are specialized interfaces to avoid autoboxing operations when the inputs or outputs are primitives.

public interface IntPredicate {
    boolean test(int t);
}

Type Checking

The type of a lambda is deduced from the context in which the lambda is used. The type expected for the lambda expression inside the context (for example, a method parameter that it’s passed to or a local variable that it’s assigned to) is called the target- type. Lambda expression can get their target type from an assignment context, method-invocation context (parameters and return), and a cast context.

Object o = (Runnable) () -> System.out.println("Hello");

Capturing lambdas

Lambdas are allowed to capture (to reference in their bodies) instance variables and static variables without restrictions. But when local variables are captured, they have to be explicitly declared final or be effectively final.

Why do we have this restriction?

Instance variables are stored on the heap, whereas local variables live on the stack. If a lambda could access the local variable directly and the lambda was used in a thread, then the thread using the lambda could try to access the variable after the thread that allocated the variable had deallocated it. Hence, Java implements access to a free local variable as access to a copy of it, rather than access to the original variable. This makes no difference if the local variable is assigned to only once—hence the restriction.

Method References

There are three main kinds of method references:

  • A method reference to a static method. Example, – Integer::parseInt
  • A method reference to an instance method of an arbitrary type. Example – String::length
  • A method reference to an instance method of an existing object or expression. Example – student::getRank where the student is the local variable of Type Student which has method getRank()
List<String> list = Arrays.asList("a","b","A","B");
list.sort((s1, s2) -> s1.compareToIgnoreCase(s2));

can be written as

List<String> list = Arrays.asList("a","b","A","B");
list.sort(String::compareToIgnoreCase);

Constructor References

Reference to existing constructor can be done using ClassName::new

Supplier<List<String>> supplier = ArrayList::new; is same as Supplier<List<String>> supplier = () -> new ArrayList<>();

Composing Lambda

Many functional interfaces contains default methods which can be used to compose lambda expressions. Example of composition –

  • Combine two predicates into a larger predicate that performs an or operation between the two predicates
  • Reverse or chain comparator

Comparators

Sorting students based on Rank in reverse order

Comparator<Student> c = Comparator.comparing(Student::getRank);
students.sort(comparing(Student::getRank).reversed()); 

Sorting students based on name (reverse) and then Rank in reverse order

students.sort(comparing(Student::getName).reversed()
        .thenComparing(Student::getRank)); 

Predicates

The Predicate interface includes three methods negateand, and or , which can be use to create more complicated predicated.

Predicate<Integer> naturalNumber = i -> i > 0;                                     
Predicate<Integer> naturalNumberLessThanHundred = naturalNumber.and( i -> i < 100);

Functions

The Function interface comes with two default methods andThen and compose.

Consider f(x) = x2 and g(x) = x3 + 1 then

g(f(x)) ->

Function<Integer,Integer> square = n -> n*n;                         
Function<Integer,Integer> squareAndCube = square.andThen(n -> n*n*n+1);
System.out.println(squareAndCube.apply(2));  
65                        

f(g(x)) ->

Function<Integer,Integer> square = n -> n*n;                              
Function<Integer,Integer> squareAndCube = square.compose(n -> n*n*n + 1); 
System.out.println(squareAndCube.apply(2));                               

Applying Lambda

Let’s see how can we write a generic method to filter a collection of books based on veratain attribute (think of this as where clauseof sql).

public static List<Book> filter(Predicate<Book> where) {                
  List<Book> books = Catalogue.books();                                 
  return books.stream().filter(where).collect(Collectors.toList());     
}                                                                       

Lambda expression to filter different books by different filter

List<Book> javaBook = filter(book -> book.getCategory().equals(JAVA));               
List<Book> joshuaBlochBook = filter(book -> book.getAuthor().equals("Joshua Bloch"));

Summary

A lambda expression can be considered an anonymous function that can be passed around which can be used in the context of a functional interface. A functional interface is an interface that specifies exactly one abstract method.

Social Share !
Default image
Pankaj
Software Architect @ Schlumberger ``` Cloud | Microservices | Programming | Kubernetes | Architecture | Machine Learning | Java | Python ```