Lambda and Functional Interfaces

Understanding Predefined Functional Interfaces in JAVA

April 6, 2020

The main focus of this blog will be coverage of four most used functional interfaces as shown in the following image.

Most Used Functional Interfaces

In the previous couple of blogs we discussed how the Lambda and functional interfaces are connected. In this blog we will throw light on predefined or preexisting interfaces. These are basically used to directly write Lambdas.

It must be clear by reading our previous blogs that as functional interface has reusable signature so there is no need to create them again, rather it can be created once and can be used everywhere with different behaviours.

For this JAVA has already provided most general reusable signatures in form of predefined interfaces and these are added to the package java.util.function. This package contains already defined GENERIC functional interfaces that can be used readily for invoking Lambda.

Following image lists the complete interfaces from the function package with their parameter specifications.

Functional interfaceFunction descriptorPrimitive specializations
Predicate<T>T -> booleanIntPredicate, LongPredicate, DoublePredicate
Consumer<T>T -> voidIntConsumer, LongConsumer, DoubleConsumer
Function<T, R>T -> RIntFunction<R>, IntToDoubleFunction, IntToLongFunction, LongFunction<R>, LongToDoubleFunction, LongToIntFunction, DoubleFunction<R>, ToIntFunction<T>, ToDoubleFunction<T>, ToLongFunction<T>
Supplier<T>() -> TBooleanSupplier, IntSupplier, LongSupplier, DoubleSupplier
UnaryOperator<T>T -> TIntUnaryOperator, LongUnaryOperator, DoubleUnaryOperator
BinaryOperator<T>
(T, T) -> TIntBinaryOperator, LongBinaryOperator, DoubleBinaryOperator
BiPredicate<L, R>(L, R) -> boolean
BiConsumer<T, U>(T, U) -> voidObjIntConsumer<T>, ObjLongConsumer<T>, ObjDoubleConsumer<T>
BiFunction<T, U, R>(T, U) -> RToIntBiFunction<T, U>, ToLongBiFunction<T, U>, ToDoubleBiFunction<T, U>

Predicate Interface

This is used for checking a condition. After checking condition it returns a boolean value i.e True / False. Thus it can take any object Type as argument but it returns a boolean. The third column of the above table has primitive specializations for predicate. This Predicate Interface is for objects.It takes an Object as argument. These primitive specializations are specially made for primitives. So if we are going to test a condition on a primitive then instead of using Predicate<T> which takes an Object type, we shall use IntPredicate and similar to intPredicate, we have Long and double Predicates. This is used to avoid the cost of boxing a primitive into an Object or unboxing an Object to a primitive type..

Consumer Interface

Consumer takes an input and never return back anything, there are some specialized consumers like IntConsumer, LongConsumer and DoubleConsumer.

Function<T, R>

Applies the Function on a given argument, and return the result

Supplier<T> 

Suppliers supplies the results..so it takes nothing it only supplies.

UnaryOperator<T>

This is an extension of Function. It represents an operation on a single operand that produces a result of the same type as its operand

BinaryOperator<T>

This is also an extension of Function. It represents an operation upon two operands of the same type, producing a result of the same type as the operands.

BiPredicate<L, R>

This is a specialized version of Predicate, it represents a predicate of two arguments

BiConsumer<T, U>

Specialization of Consumer , it represents an operation that accepts two input arguments and returns no result

BiFunction<T, U, R>

Specialization of Function, it represents a function that accepts two arguments and produces a result

The Predicate, Consumer, Function and Supplier are the most used interfaces. We will discuss these in detail later on.

First let us discuss the functional interfaces with Generic Syntax. Consider the following code

@FunctionalInterface
public interface FunctionalGenerics {
    String execute(String t);
}

The above interface is not Generic. To make it generic, it must have some type parameters. So we add T & R. T is for the type of Input argument to the method execute. And R is for the return type of the execute method.

@FunctionalInterface
public interface FunctionalGenerics <T ,R > {
    R execute(T t); 
}

Now let us see how Lambda can be implemented for this Generic Interface. The Lambda is the substring method which returns the substring of the passed string

public class FunctionalGenericsIDemo {
	public static void main(String[] args) {
             FunctionalGenerics<String, String> fun = (s) -> s.substring(1, 5);
             System.out.println(fun.execute("BasicsStrong"));
         }
}

As this lambda accepts a String and returns a String. So T & R are taken as String type .
The output of this code will be “asic” i.e the substring of the string “BasicsStrong from index 1 to 5.

Let us create another Lambda for String as T and Integer as R which takes a string and returns the length of the string

public class FunctionalGenericsIDemo {
	public static void main(String[] args) {
             FunctionalGenerics<String, Integer> fun1 = (s) -> s.length();
             System.out.println(fun1.execute("BasicsStrong"));
         }
}

This displays the length of the string i.e 12.

Thus we noticed that how one Generic Functional Interface can give amazing flexibility. It can be used with any Object type. We can take any type as T and any Type as R as well. Hence we can create as many behaviours as we want that use the same signature.

Now let us discuss the predefined interfaces one by one.

Predicate

Predicate is a Functional Interface that defines an abstract method named Test. It is used to test some condition that’s why it returns a boolean that accepts an object of generic type T and returns a boolean.

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

Now Let us consider a problem statement to practice predicate. The use case is to return a new List by removing all the empty strings from a list of String values.

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

public class PracticePredicate {
	public static void main(String[] args) 
            {
               List<String> list = new ArrayList<>();
               list.add("Basics");
               list.add("");
              list.add("Strong");
               list.add("");
              Predicate<String> predicate = s ->!s.isEmpty();
              List<String> newList = filterList(list, predicate);  
            System.out.println(newList); 
           }           
        private static List<String> filterList(List<String>  list, Predicate<String>   predicate{
                     List<String> newList = new ArrayList<>();

                     for(String string : list) {
                           if(predicate.test(string)) {
                               newList.add(string);
                          }
                   }
                     return null;
                 }
}

This returns the string [Basics, Strong]

Now to make it Generic we can replace String by T which means it can be used for any type T.

private static <T>List<T> filterList(List<T>  list, Predicate<T> predicate{
                     List<T> newList = new ArrayList<>();
                     for(T string : list) {
                           if(predicate.test(string)) {
                               newList.add(string);
                          }
                   }
                     return null;
                 }
}

Then suppose we have an integer list and an integer predicate and we want to write a filter for integers which checks whether the element is Even /Odd and returns a new list.

List<Integer> intList = List.of(1,4,6,7,8);
Predicate<Integer>integerFilter = e -> e % 2 ==0;

Now if we call filter List function

List<Integer>evens = filterList( intList, integerFilter );

Then when we display the contents of the evens list

System.out.println(evens);

It will display the even values i.e [4,6,8].

This is how the predicate interface works to test a condition.

Consumer

Consumer is a Functional Interface that defines an abstract method named Accept. It accepts an object of generic type T and returns nothing.

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

Now Let us consider a problem statement to practice consumer. The use case is to create a list of integers. The consumer interface can be used to perform any operation without returning anything. So let us create a list and display it using Consumer.

import java.util.List;
import java.util.function.Consumer;

public class ConsumerPractice {
     public static void main(String[] args) {
         List<Integer> list = list.of ( 2,32,23,54,56);
         Consumer<Integer> consumer = e ->System.out.println(e);
         consumer.accept(34);
    }
}

This displays the value 34.

To print all the elements of the list, we will create a generic print elements method

private static <T> void  printElements(List<T> list , Consumer<T> consumer)
{
  for (T t : list)
  {
     consumer.accept(t);
  }
}
 

When we invoke the method printElements inside the main function

printElements(list, consumer);

This displays all the elements present in the list

i.e 2,32,23,54,56

Supplier

Supplier is a Functional Interface that defines an abstract method named Get. It takes nothing but returns something.This interface is used to produce results without taking any input .

@FunctionalInterface
public Interface Supplier<T>{
    T get();
}

Now Let us consider a problem statement to practice supplier.

 import java.util.function.Supplier;
 public class SupplierPractice {
	
	public static void main(String[] args) {
		Supplier<String> supplier = () -> new String(“An Object”);
                System.out.println("stringSupplier.get();
               }
} 

So whenever we need any object we can get it by invoking the get function of Supplier interface. This code will simply display “An Object”.

Suppose we want to return Random Numbers

Supplier<Double> randomNumber = () -> Math.random();

When we need random number anywhere in the program we can get it by

System.out.println(randomNumber.get();

Function

Function is the Functional Interface that can take something and can also return something. Generally Speaking, Function is used for transformation. It has a method Apply which takes an object of generic type T and transform it to an object of generic type R.

They are not using T at both places. So the returned Object can be of same type or it can be of different type as well.

@FunctionalInterface
public interface Function<T, R> {                       
   R apply(T t); 
}

Now lets use this functional interface. The use case is to create a list of Strings and write a function which takes this list as input and returns list of length of individual strings. So its a Function with T as String and R as Integer.

public class FunctionPractice {
	public static void main(String[] args) {
		
                e->e.length();  
                Function<String,Integer> function1 = e->e.length(); 
				
		List<String> list=Arrays.asList(“Kit”, “Kat”, “Shake”);
		
		List<Integer> newList= map(list, function1);
	}
	

Now we create a new list and make it a generic function . The elements are added after transformation to this list and list is returned.

private static <T,R>List<R> map(List<T> list, Function<T,R> function) {

    List<R> newList = new ArrayList<R>();
    for(T t: list)
    {
        newList.add(function.apply(t));
    }
    return newList;
}

When we invoke the function

System.out.println(newList);

It returns [3,3,5] which is the length of the string list [Kit , Kat , Shake]

Unary Operator

The UnaryOperator is a specialization of Function Interface. The UnaryOperator Functional Interface extends the functional Interface and restrict it to return same type as of the input Parameter. Unary operator accepts one type and return the same type.

@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> { 

}

Now let us write a UnaryOperator which takes an integer, multiply it by 100. Lets take a list of integers and write a function which takes the list and returns a new list by multiplying each element of the list by 100.

public class UnaryOperatorPractice {	
	

	    public static void main(String[] args) {
	       
	       List<Integer> list = Arrays.asList(10,20,30,40,50);	       
	       UnaryOperator<Integer> operator = i -> i*100;
	       List<Integer> newList = mapper(list, operator);
unaryOperatorFun(unaryOpt, list).forEach(x->System.out.println(x));       
	    }
	    	   

Now let us create a generic function Mapper.

private static  <T>List<T>mapper(List<T> list, UnaryOperator<T> operator)
{ 
    List <T> newList = newArrayList<>();
    for (T t : list)
    { 
     T ele = operator.apply(t);
     newList.add(ele);
  }    
return newList;
}

When we invoke this function

System.out.println(newList);

This displays the transformed list by multiplying each element by 100 i.e [1000,2000,3000,4000,5000]

BiFunction

BiFunction is also a kind of a Specialized Function that takes two Type of parameters and produces the third type of parameter; It does not has any restriction on type of the return value.

@FunctionalInterface
public interface BiFunction<T, U, R> {
    R apply(T t, U u);
}

Let us practice this. The use case is to write a biFunction which takes two strings and returns the concatenation of these as a new string.

public class BiFunctionPractice {    

      public static void main(String[] args) {
              BiFunction<String, String, String> biFunction = (s1, s2) -> s1 + s2;
             biFunction.apply("Basics","Strong");
             System.out.println(function1.apply("Basics", "Strong"));
         }
   }

This displays “Basics Strong”

To return the length of the resultant string

BiFunction<String, String, Integer> biFunction = (s1, s2) -> (s1 + s2).Length();

It displays 12 which is the length of the concatenated string.

BinaryOperator

Binary Operator is a specialized type of BiFunction that takes same type of arguments and produces same. It also puts a restriction on the type of return type thus it represents an operation upon two operands of the same type, producing a result of the same type as the operands.

@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> { 

}

Let us practice this. The use case is to write a function which takes two string operators and returning the concatenated string.

public class BinaryOperatorPractice {

	public static void main(String[] args) {   
	     BinaryOperator<String> binaryOperator = (s1,s2)-> s1+"."+s2;	     
	     String result = binaryOperator.apply("BasicsStrong", "com");
	     System.out.println(result);
	    }
}

This displays the string “BasicsStrong.com”

To summarize, this blog discussed various predefined interfaces and their usage.

Stay Connected for Next Blog !!