The main focus of this blog will be coverage of four most used functional interfaces as shown in the following image.
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 interface | Function descriptor | Primitive specializations |
Predicate<T> | T -> boolean | IntPredicate, LongPredicate, DoublePredicate |
Consumer<T> | T -> void | IntConsumer, LongConsumer, DoubleConsumer |
Function<T, R> | T -> R | IntFunction<R>, IntToDoubleFunction, IntToLongFunction, LongFunction<R>, LongToDoubleFunction, LongToIntFunction, DoubleFunction<R>, ToIntFunction<T>, ToDoubleFunction<T>, ToLongFunction<T> |
Supplier<T> | () -> T | BooleanSupplier, IntSupplier, LongSupplier, DoubleSupplier |
UnaryOperator<T> | T -> T | IntUnaryOperator, LongUnaryOperator, DoubleUnaryOperator |
BinaryOperator<T> | (T, T) -> T | IntBinaryOperator, LongBinaryOperator, DoubleBinaryOperator |
BiPredicate<L, R> | (L, R) -> boolean | |
BiConsumer<T, U> | (T, U) -> void | ObjIntConsumer<T>, ObjLongConsumer<T>, ObjDoubleConsumer<T> |
BiFunction<T, U, R> | (T, U) -> R | ToIntBiFunction<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 !!