”;
Functional Programming – Overview
In functional programming paradigm, an application is written mostly using pure functions. Here pure function is a function having no side effects. An example of side effect is modification of instance level variable while returning a value from the function.
Following are the key aspects of functional programming.
-
Functions − A function is a block of statements that performs a specific task. Functions accept data, process it, and return a result. Functions are written primarily to support the concept of re usability. Once a function is written, it can be called easily, without having to write the same code again and again.
Functional Programming revolves around first class functions, pure functions and high order functions.
-
A First Class Function is the one that uses first class entities like String, numbers which can be passed as arguments, can be returned or assigned to a variable.
-
A High Order Function is the one which can either take a function as an argument and/or can return a function.
-
A Pure Function is the one which has no side effect while its execution.
-
-
Functional Composition − In imperative programming, functions are used to organize an executable code and emphasis is on organization of code. But in functional programming, emphasis is on how functions are organized and combined. Often data and functions are passed together as arguments and returned. This makes programming more capable and expressive.
-
Fluent Interfaces − Fluent interfaces helps in composing expressions which are easy to write and understand. These interfaces helps in chaining the method call when each method return type is again reused. For example −
LocalDate futureDate = LocalDate.now().plusYears(2).plusDays(3);
-
Eager vs Lazy Evaluation − Eager evaluation means expressions are evaluated as soon as they are encountered whereas lazy evaluation refers to delaying the execution till certain condition is met. For example, stream methods in Java 8 are evaluated when a terminal method is encountered.
-
Persistent Data Structures
− A persistent data structure maintains its previous version. Whenever data structure state is changed, a new copy of structure is created so data structure remains effectively immutable. Such immutable collections are thread safe.
-
Recursion − A repeated calculation can be done by making a loop or using recursion more elegantly. A function is called recursive function if it calls itself.
-
Parallelism − Functions with no side effects can be called in any order and thus are candidate of lazy evaluation. Functional programming in Java supports parallelism using streams where parallel processing is provided.
-
Optionals − Optional is a special class which enforces that a function should never return null. It should return value using Optional class object. This returned object has method isPresent which can be checked to get the value only if present.
Functional Programming with Java – Functions
A function is a block of statements that performs a specific task. Functions accept data, process it, and return a result. Functions are written primarily to support the concept of re usability. Once a function is written, it can be called easily, without having to write the same code again and again.
Functional Programming revolves around first class functions, pure functions and high order functions.
-
A First Class Function is the one that uses first class entities like String, numbers which can be passed as arguments, can be returned or assigned to a variable.
-
A High Order Function is the one which can take a function as an argument and/or can return a function.
-
A Pure Function is the one which has no side effect while its execution.
First Class Function
A first class function can be treated as a variable. That means it can be passed as a parameter to a function, it can be returned by a function or can be assigned to a variable as well. Java supports first class function using lambda expression. A lambda expression is analogous to an anonymous function. See the example below −
public class FunctionTester { public static void main(String[] args) { int[] array = {1, 2, 3, 4, 5}; SquareMaker squareMaker = item -> item * item; for(int i = 0; i < array.length; i++){ System.out.println(squareMaker.square(array[i])); } } } interface SquareMaker { int square(int item); }
Output
1 4 9 16 25
Here we have created the implementation of square function using a lambda expression and assigned it to variable squareMaker.
High Order Function
A high order function either takes a function as a parameter or returns a function. In Java, we can pass or return a lambda expression to achieve such functionality.
import java.util.function.Function; public class FunctionTester { public static void main(String[] args) { int[] array = {1, 2, 3, 4, 5}; Function<Integer, Integer> square = t -> t * t; Function<Integer, Integer> cube = t -> t * t * t; for(int i = 0; i < array.length; i++){ print(square, array[i]); } for(int i = 0; i < array.length; i++){ print(cube, array[i]); } } private static <T, R> void print(Function<T, R> function, T t ) { System.out.println(function.apply(t)); } }
Output
1 4 9 16 25 1 8 27 64 125
Pure Function
A pure function does not modify any global variable or modify any reference passed as a parameter to it. So it has no side-effect. It always returns the same value when invoked with same parameters. Such functions are very useful and are thread safe. In example below, sum is a pure function.
public class FunctionTester { public static void main(String[] args) { int a, b; a = 1; b = 2; System.out.println(sum(a, b)); } private static int sum(int a, int b){ return a + b; } }
Output
3
Functional Programming with Java – Composition
Functional composition refers to a technique where multiple functions are combined together to a single function. We can combine lambda expression together. Java provides inbuilt support using Predicate and Function classes. Following example shows how to combine two functions using predicate approach.
import java.util.function.Predicate; public class FunctionTester { public static void main(String[] args) { Predicate<String> hasName = text -> text.contains("name"); Predicate<String> hasPassword = text -> text.contains("password"); Predicate<String> hasBothNameAndPassword = hasName.and(hasPassword); String queryString = "name=test;password=test"; System.out.println(hasBothNameAndPassword.test(queryString)); } }
Output
true
Predicate provides and() and or() method to combine functions. Whereas Function provides compose and andThen methods to combine functions. Following example shows how to combine two functions using Function approach.
import java.util.function.Function; public class FunctionTester { public static void main(String[] args) { Function<Integer, Integer> multiply = t -> t *3; Function<Integer, Integer> add = t -> t + 3; Function<Integer, Integer> FirstMultiplyThenAdd = multiply.compose(add); Function<Integer, Integer> FirstAddThenMultiply = multiply.andThen(add); System.out.println(FirstMultiplyThenAdd.apply(3)); System.out.println(FirstAddThenMultiply.apply(3)); } }
Output
18 12
Eager vs Lazy Evaluation
Eager evaluation means expression is evaluated as soon as it is encountered where as lazy evaluation refers to evaluation of an expression when needed. See the following example to under the concept.
import java.util.function.Supplier; public class FunctionTester { public static void main(String[] args) { String queryString = "password=test"; System.out.println(checkInEagerWay(hasName(queryString) , hasPassword(queryString))); System.out.println(checkInLazyWay(() -> hasName(queryString) , () -> hasPassword(queryString))); } private static boolean hasName(String queryString){ System.out.println("Checking name: "); return queryString.contains("name"); } private static boolean hasPassword(String queryString){ System.out.println("Checking password: "); return queryString.contains("password"); } private static String checkInEagerWay(boolean result1, boolean result2){ return (result1 && result2) ? "all conditions passed": "failed."; } private static String checkInLazyWay(Supplier<Boolean> result1, Supplier<Boolean> result2){ return (result1.get() && result2.get()) ? "all conditions passed": "failed."; } }
Output
Checking name: Checking password: failed. Checking name: failed.
Here checkInEagerWay() function first evaluated the parameters then executes its statement. Whereas checkInLazyWay() executes its statement and evaluates the parameter on need basis. As && is a short-circuit operator, checkInLazyWay only evaluated first parameter which comes as false and does not evaluate the second parameter at all.
Persistent Data Structure
A data structure is said to be persistent if it is capable to maintaining its previous updates as separate versions and each version can be accessed and updated accordingly. It makes the data structure immutable and thread safe. For example, String class object in Java is immutable. Whenever we make any change to string, JVM creates another string object, assigned it the new value and preserve the older value as old string object.
A persistent data structure is also called a functional data structure. Consider the following case −
Non-Persistent way
public static Person updateAge(Person person, int age){ person.setAge(age); return person; }
Persistent way
public static Person updateAge(Person pPerson, int age){ Person person = new Person(); person.setAge(age); return person; }
Functional Programming with Java – Recursion
Recursion is calling a same function in a function until certain condition are met. It helps in breaking big problem into smaller ones. Recursion also makes code more readable and expressive.
Imperative vs Recursive
Following examples shows the calculation of sum of natural numbers using both the techniques.
public class FunctionTester { public static void main(String[] args) { System.out.println("Sum using imperative way. Sum(5) : " + sum(5)); System.out.println("Sum using recursive way. Sum(5) : " + sumRecursive(5)); } private static int sum(int n){ int result = 0; for(int i = 1; i <= n; i++){ result = result + i; } return result; } private static int sumRecursive(int n){ if(n == 1){ return 1; }else{ return n + sumRecursive(n-1); } } }
Output
Sum using imperative way. Sum(5) : 15 Sum using recursive way. Sum(5) : 15
Using recursion, we are adding the result of sum of n-1 natural numbers with n to get the required result.
Tail Recursion
Tail recursion says that recursive method call should be at the end. Following examples shows the printing of a number series using tail recursion.
public class FunctionTester { public static void main(String[] args) { printUsingTailRecursion(5); } public static void printUsingTailRecursion(int n){ if(n == 0) return; else System.out.println(n); printUsingTailRecursion(n-1); } }
Output
5 4 3 2 1
Head Recursion
Head recursion says that recursive method call should be in the beginning of the code. Following examples shows the printing of a number series using head recursion.
public class FunctionTester { public static void main(String[] args) { printUsingHeadRecursion(5); } public static void printUsingHeadRecursion(int n){ if(n == 0) return; else printUsingHeadRecursion(n-1); System.out.println(n); } }
Output
1 2 3 4 5
Functional Programming with Java – Parallelism
Parallelism is a key concept of functional programming where a big task is accomplished by breaking in smaller independent tasks and then these small tasks are completed in a parallel fashion and later combined to give the complete result. With the advent of multi-core processors, this technique helps in faster code execution. Java has Thread based programming support for parallel processing but it is quite tedious to learn and difficult to implement without bugs. Java 8 onwards, stream have parallel method and collections has parallelStream() method to complete tasks in parallel fashion. See the example below:
import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class FunctionTester { public static void main(String[] args) { Integer[] intArray = {1, 2, 3, 4, 5, 6, 7, 8 }; List<Integer> listOfIntegers = new ArrayList<>(Arrays.asList(intArray)); System.out.println("List using Serial Stream:"); listOfIntegers .stream() .forEach(e -> System.out.print(e + " ")); System.out.println(""); System.out.println("List using Parallel Stream:"); listOfIntegers .parallelStream() .forEach(e -> System.out.print(e + " ")); System.out.println(""); System.out.println("List using Another Parallel Stream:"); listOfIntegers .stream() .parallel() .forEach(e -> System.out.print(e + " ")); System.out.println(""); System.out.println("List using Parallel Stream but Ordered:"); listOfIntegers .parallelStream() .forEachOrdered(e -> System.out.print(e + " ")); System.out.println(""); } }
Output
List using Serial Stream: 1 2 3 4 5 6 7 8 List using Parallel Stream: 6 5 8 7 3 4 2 1 List using Another Parallel Stream: 6 2 1 7 4 3 8 5 List using Parallel Stream but Ordered: 1 2 3 4 5 6 7 8
Optionals and Monads
Monad is a key concept of Functional Programming. A Monad is a design pattern which helps to represent a missing value. It allows to wrap a potential null value, allows to put transformation around it and pull actual value if present. By definition, a monad is a set of following parameters.
-
A parametrized Type − M<T>
-
A unit Function − T −> M<T>
-
A bind operation − M<T> bind T −> M<U> = M<U>
Key Operations
-
Left Identity − If a function is bind on a monad of a particular value then its result will be same as if function is applied to the value.
-
Right Identity − If a monad return method is same as monad on original value.
-
Associativity − Functions can be applied in any order on a monad.
Optional Class
Java 8 introduced Optional class which is a monad. It provides operational equivalent to a monad. For example return is a operation which takes a value and return the monad. Optional.of() takes a parameters and returns the Optional Object. On similar basis , bind is operation which binds a function to a monad to produce a monad. Optional.flatMap() is the method which performs an operation on Optional and return the result as Optional.
-
A parametrized Type − Optional<T>
-
A unit Function − Optional.of()
-
A bind operation − Optional.flatMap()
Example − Left Identity
Following example shows how Optional class obeys Left Identity rule.
import java.util.Optional; import java.util.function.Function; public class FunctionTester { public static void main(String[] args) { Function<Integer, Optional<Integer>> addOneToX = x −> Optional.of(x + 1); System.out.println(Optional.of(5).flatMap(addOneToX) .equals(addOneToX.apply(5))); } }
Output
true
Example − Right Identity
Following example shows how Optional class obeys Right Identity rule.
import java.util.Optional; public class FunctionTester { public static void main(String[] args) { System.out.println(Optional.of(5).flatMap(Optional::of) .equals(Optional.of(5))); } }
Output
true
Example – Associativity
Following example shows how Optional class obeys Associativity rule.
import java.util.Optional; import java.util.function.Function; public class FunctionTester { public static void main(String[] args) { Function<Integer, Optional<Integer>> addOneToX = x −> Optional.of(x + 1); Function<Integer, Optional<Integer>> addTwoToX = x −> Optional.of(x + 2); Function<Integer, Optional<Integer>> addThreeToX = x −> addOneToX.apply(x).flatMap(addTwoToX); Optional.of(5).flatMap(addOneToX).flatMap(addTwoToX) .equals(Optional.of(5).flatMap(addThreeToX)); } }
Output
true
Functional Programming with Java – Closure
A closure is a function which is a combination of function along with its surrounding state. A closure function generally have access to outer function”s scope. In the example given below, we have created a function getWeekDay(String[] days) which returns a function which can return the text equivalent of a weekday. Here getWeekDay() is a closure which is returning a function surrounding the calling function”s scope.
Following example shows how Closure works.
import java.util.function.Function; public class FunctionTester { public static void main(String[] args) { String[] weekDays = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }; Function<Integer, String> getIndianWeekDay = getWeekDay(weekDays); System.out.println(getIndianWeekDay.apply(6)); } public static Function<Integer, String> getWeekDay(String[] weekDays){ return index -> index >= 0 ? weekDays[index % 7] : null; } }
Output
Sunday
Functional Programming with Java – Currying
Currying is a technique where a many arguments function call is replaced with multiple method calls with lesser arguments.
See the below equation.
(1 + 2 + 3) = 1 + (2 + 3) = 1 + 5 = 6
In terms of functions:
f(1,2,3) = g(1) + h(2 + 3) = 1 + 5 = 6
This cascading of functions is called currying and calls to cascaded functions must gives the same result as by calling the main function.
Following example shows how Currying works.
import java.util.function.Function; public class FunctionTester { public static void main(String[] args) { Function<Integer, Function<Integer, Function<Integer, Integer>>> addNumbers = u -> v -> w -> u + v + w; int result = addNumbers.apply(2).apply(3).apply(4); System.out.println(result); } }
Output
9
Functional Programming with Java – Reducing
In functional programming, reducing is a technique to reduce a stream of values to a single result by apply a function on all the values. Java provides reduce() function in a Stream class from Java 8 onwards. A stream has inbuilt reducing methods like sum(), average(), count() as well which works on all elements of the stream and returns the single result.
Following example shows how Reducing works.
import java.util.stream.IntStream; public class FunctionTester { public static void main(String[] args) { //1 * 2 * 3 * 4 = 24 int product = IntStream.range(1, 5) .reduce((num1, num2) -> num1 * num2) .orElse(-1); //1 + 2 + 3 + 4 = 10 int sum = IntStream.range(1, 5).sum(); System.out.println(product); System.out.println(sum); } }
Output
24 10
Functional Programming – Lambda Expressions
Lambda expressions are introduced in Java 8 and are touted to be the biggest feature of Java 8. Lambda expression facilitates functional programming, and simplifies the development a lot.
Syntax
A lambda expression is characterized by the following syntax.
parameter -> expression body
Following are the important characteristics of a lambda expression.
-
Optional type declaration − No need to declare the type of a parameter. The compiler can inference the same from the value of the parameter.
-
Optional parenthesis around parameter − No need to declare a single parameter in parenthesis. For multiple parameters, parentheses are required.
-
Optional curly braces − No need to use curly braces in expression body if the body contains a single statement.
-
Optional return keyword − The compiler automatically returns the value if the body has a single expression to return the value. Curly braces are required to indicate that expression returns a value.
Lambda Expressions Example
Create the following Java program using any editor of your choice in, say, C:> JAVA.
Java8Tester.java
public class Java8Tester { public static void main(String args[]) { Java8Tester tester = new Java8Tester(); //with type declaration MathOperation addition = (int a, int b) -> a + b; //with out type declaration MathOperation subtraction = (a, b) -> a - b; //with return statement along with curly braces MathOperation multiplication = (int a, int b) -> { return a * b; }; //without return statement and without curly braces MathOperation division = (int a, int b) -> a / b; System.out.println("10 + 5 = " + tester.operate(10, 5, addition)); System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction)); System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication)); System.out.println("10 / 5 = " + tester.operate(10, 5, division)); //without parenthesis GreetingService greetService1 = message -> System.out.println("Hello " + message); //with parenthesis GreetingService greetService2 = (message) -> System.out.println("Hello " + message); greetService1.sayMessage("Mahesh"); greetService2.sayMessage("Suresh"); } interface MathOperation { int operation(int a, int b); } interface GreetingService { void sayMessage(String message); } private int operate(int a, int b, MathOperation mathOperation) { return mathOperation.operation(a, b); } }
Verify the Result
Compile the class using javac compiler as follows −
C:JAVA>javac Java8Tester.java
Now run the Java8Tester as follows −
C:JAVA>java Java8Tester
It should produce the following output −
10 + 5 = 15 10 - 5 = 5 10 x 5 = 50 10 / 5 = 2 Hello Mahesh Hello Suresh
Following are the important points to be considered in the above example.
-
Lambda expressions are used primarily to define inline implementation of a functional interface, i.e., an interface with a single method only. In the above example, we”ve used various types of lambda expressions to define the operation method of MathOperation interface. Then we have defined the implementation of sayMessage of GreetingService.
-
Lambda expression eliminates the need of anonymous class and gives a very simple yet powerful functional programming capability to Java.
Scope
Using lambda expression, you can refer to any final variable or effectively final variable (which is assigned only once). Lambda expression throws a compilation error, if a variable is assigned a value the second time.
Scope Example
Create the following Java program using any editor of your choice in, say, C:> JAVA.
Java8Tester.java
public class Java8Tester { final static String salutation = "Hello! "; public static void main(String args[]) { GreetingService greetService1 = message -> System.out.println(salutation + message); greetService1.sayMessage("Mahesh"); } interface GreetingService { void sayMessage(String message); } }
Verify the Result
Compile the class using javac compiler as follows −
C:JAVA>javac Java8Tester.java
Now run the Java8Tester as follows −
C:JAVA>java Java8Tester
It should produce the following output −
Hello! Mahesh
Functional Programming – Default Methods
Java 8 introduces a new concept of default method implementation in interfaces. This capability is added for backward compatibility so that old interfaces can be used to leverage the lambda expression capability of Java 8.
For example, ”List” or ”Collection” interfaces do not have ”forEach” method declaration. Thus, adding such method will simply break the collection framework implementations. Java 8 introduces default method so that List/Collection interface can have a default implementation of forEach method, and the class implementing these interfaces need not implement the same.
Syntax
public interface vehicle { default void print() { System.out.println("I am a vehicle!"); } }
Multiple Defaults
With default functions in interfaces, there is a possibility that a class is implementing two interfaces with same default methods. The following code explains how this ambiguity can be resolved.
public interface vehicle { default void print() { System.out.println("I am a vehicle!"); } } public interface fourWheeler { default void print() { System.out.println("I am a four wheeler!"); } }
First solution is to create an own method that overrides the default implementation.
public class car implements vehicle, fourWheeler { public void print() { System.out.println("I am a four wheeler car vehicle!"); } }
Second solution is to call the default method of the specified interface using super.
public class car implements vehicle, fourWheeler { default void print() { vehicle.super.print(); } }
Static Default Methods
An interface can also have static helper methods from Java 8 onwards.
public interface vehicle { default void print() { System.out.println("I am a vehicle!"); } static void blowHorn() { System.out.println("Blowing horn!!!"); } }
Default Method Example
Create the following Java program using any editor of your choice in, say, C:> JAVA.
Java8Tester.java
public class Java8Tester { public static void main(String args[]) { Vehicle vehicle = new Car(); vehicle.print(); } } interface Vehicle { default void print() { System.out.println("I am a vehicle!"); } static void blowHorn() { System.out.println("Blowing horn!!!"); } } interface FourWheeler { default void print() { System.out.println("I am a four wheeler!"); } } class Car implements Vehicle, FourWheeler { public void print() { Vehicle.super.print(); FourWheeler.super.print(); Vehicle.blowHorn(); System.out.println("I am a car!"); } }
Verify the Result
Compile the class using javac compiler as follows −
C:JAVA>javac Java8Tester.java
Now run the Java8Tester as follows −
C:JAVA>java Java8Tester
It should produce the following output −
I am a vehicle! I am a four wheeler! Blowing horn!!! I am a car!
Functional Programming – Functional Interfaces
Functional interfaces have a single functionality to exhibit. For example, a Comparable interface with a single method ”compareTo” is used for comparison purpose. Java 8 has defined a lot of functional interfaces to be used extensively in lambda expressions. Following is the list of functional interfaces defined in java.util.Function package.
Sr.No. | Interface & Description |
---|---|
1 |
BiConsumer<T,U> Represents an operation that accepts two input arguments, and returns no result. |
2 |
BiFunction<T,U,R> Represents a function that accepts two arguments and produces a result. |
3 |
BinaryOperator<T> Represents an operation upon two operands of the same type, producing a result of the same type as the operands. |
4 |
BiPredicate<T,U> Represents a predicate (Boolean-valued function) of two arguments. |
5 |
BooleanSupplier Represents a supplier of Boolean-valued results. |
6 |
Consumer<T> Represents an operation that accepts a single input argument and returns no result. |
7 |
DoubleBinaryOperator Represents an operation upon two double-valued operands and producing a double-valued result. |
8 |
DoubleConsumer Represents an operation that accepts a single double-valued argument and returns no result. |
9 |
DoubleFunction<R> Represents a function that accepts a double-valued argument and produces a result. |
10 |
DoublePredicate Represents a predicate (Boolean-valued function) of one double-valued argument. |
11 |
DoubleSupplier Represents a supplier of double-valued results. |
12 |
DoubleToIntFunction Represents a function that accepts a double-valued argument and produces an int-valued result. |
13 |
DoubleToLongFunction Represents a function that accepts a double-valued argument and produces a long-valued result. |
14 |
DoubleUnaryOperator Represents an operation on a single double-valued operand that produces a double-valued result. |
15 |
Function<T,R> Represents a function that accepts one argument and produces a result. |
16 |
IntBinaryOperator Represents an operation upon two int-valued operands and produces an int-valued result. |
17 |
IntConsumer Represents an operation that accepts a single int-valued argument and returns no result. |
18 |
IntFunction<R> Represents a function that accepts an int-valued argument and produces a result. |
19 |
IntPredicate Represents a predicate (Boolean-valued function) of one int-valued argument. |
20 |
IntSupplier Represents a supplier of int-valued results. |
21 |
IntToDoubleFunction Represents a function that accepts an int-valued argument and produces a double-valued result. |
22 |
IntToLongFunction Represents a function that accepts an int-valued argument and produces a long-valued result. |
23 |
IntUnaryOperator Represents an operation on a single int-valued operand that produces an int-valued result. |
24 |
LongBinaryOperator Represents an operation upon two long-valued operands and produces a long-valued result. |
25 |
LongConsumer Represents an operation that accepts a single long-valued argument and returns no result. |
26 |
LongFunction<R> Represents a function that accepts a long-valued argument and produces a result. |
27 |
LongPredicate Represents a predicate (Boolean-valued function) of one long-valued argument. |
28 |
LongSupplier Represents a supplier of long-valued results. |
29 |
LongToDoubleFunction Represents a function that accepts a long-valued argument and produces a double-valued result. |
30 |
LongToIntFunction Represents a function that accepts a long-valued argument and produces an int-valued result. |
31 |
LongUnaryOperator Represents an operation on a single long-valued operand that produces a long-valued result. |
32 |
ObjDoubleConsumer<T> Represents an operation that accepts an object-valued and a double-valued argument, and returns no result. |
33 |
ObjIntConsumer<T> Represents an operation that accepts an object-valued and an int-valued argument, and returns no result. |
34 |
ObjLongConsumer<T> Represents an operation that accepts an object-valued and a long-valued argument, and returns no result. |
35 |
Predicate<T> Represents a predicate (Boolean-valued function) of one argument. |
36 |
Supplier<T> Represents a supplier of results. |
37 |
ToDoubleBiFunction<T,U> Represents a function that accepts two arguments and produces a double-valued result. |
38 |
ToDoubleFunction<T> Represents a function that produces a double-valued result. |
39 |
ToIntBiFunction<T,U> Represents a function that accepts two arguments and produces an int-valued result. |
40 |
ToIntFunction<T> Represents a function that produces an int-valued result. |
41 |
ToLongBiFunction<T,U> Represents a function that accepts two arguments and produces a long-valued result. |
42 |
ToLongFunction<T> Represents a function that produces a long-valued result. |
43 |
UnaryOperator<T> Represents an operation on a single operand that produces a result of the same type as its operand. |
Functional Interface Example
Predicate <T> interface is a functional interface with a method test(Object) to return a Boolean value. This interface signifies that an object is tested to be true or false.
Create the following Java program using any editor of your choice in, say, C:> JAVA.
Java8Tester.java
import java.util.Arrays; import java.util.List; import java.util.function.Predicate; public class Java8Tester { public static void main(String args[]) { List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9); // Predicate<Integer> predicate = n -> true // n is passed as parameter to test method of Predicate interface // test method will always return true no matter what value n has. System.out.println("Print all numbers:"); //pass n as parameter eval(list, n->true); // Predicate<Integer> predicate1 = n -> n%2 == 0 // n is passed as parameter to test method of Predicate interface // test method will return true if n%2 comes to be zero System.out.println("Print even numbers:"); eval(list, n-> n%2 == 0 ); // Predicate<Integer> predicate2 = n -> n > 3 // n is passed as parameter to test method of Predicate interface // test method will return true if n is greater than 3. System.out.println("Print numbers greater than 3:"); eval(list, n-> n > 3 ); } public static void eval(List<Integer> list, Predicate<Integer> predicate) { for(Integer n: list) { if(predicate.test(n)) { System.out.println(n + " "); } } } }
Here we”ve passed Predicate interface, which takes a single input and returns Boolean.
Verify the Result
Compile the class using javac compiler as follows −
C:JAVA>javac Java8Tester.java
Now run the Java8Tester as follows −
C:JAVA>java Java8Tester
It should produce the following output −
Print all numbers: 1 2 3 4 5 6 7 8 9 Print even numbers: 2 4 6 8 Print numbers greater than 3: 4 5 6 7 8 9
Functional Programming – Method References
Method references help to point to methods by their names. A method reference is described using “::” symbol. A method reference can be used to point the following types of methods −
-
Static methods – static method can be reference using ClassName::Method name notation.
//Method Reference - Static way Factory vehicle_factory_static = VehicleFactory::prepareVehicleInStaticMode;
-
Instance methods – instance method can be reference using Object::Method name notation.
//Method Reference - Instance way Factory vehicle_factory_instance = new VehicleFactory()::prepareVehicle;
Following example shows how Method references works in Java 8 onwards.
interface Factory { Vehicle prepare(String make, String model, int year); } class Vehicle { private String make; private String model; private int year; Vehicle(String make, String model, int year){ this.make = make; this.model = model; this.year = year; } public String toString(){ return "Vehicle[" + make +", " + model + ", " + year+ "]"; } } class VehicleFactory { static Vehicle prepareVehicleInStaticMode(String make, String model, int year){ return new Vehicle(make, model, year); } Vehicle prepareVehicle(String make, String model, int year){ return new Vehicle(make, model, year); } } public class FunctionTester { public static void main(String[] args) { //Method Reference - Static way Factory vehicle_factory_static = VehicleFactory::prepareVehicleInStaticMode; Vehicle carHyundai = vehicle_factory_static.prepare("Hyundai", "Verna", 2018); System.out.println(carHyundai); //Method Reference - Instance way Factory vehicle_factory_instance = new VehicleFactory()::prepareVehicle; Vehicle carTata = vehicle_factory_instance.prepare("Tata", "Harrier", 2019); System.out.println(carTata); } }
Output
Vehicle[Hyundai, Verna, 2018] Vehicle[Tata, Harrier, 2019]
Constructor References
Constructor references help to point to Constructor method. A Constructor reference is accessed using “::new” symbol.
//Constructor reference Factory vehicle_factory = Vehicle::new;
Following example shows how Constructor references works in Java 8 onwards.
interface Factory { Vehicle prepare(String make, String model, int year); } class Vehicle { private String make; private String model; private int year; Vehicle(String make, String model, int year){ this.make = make; this.model = model; this.year = year; } public String toString(){ return "Vehicle[" + make +", " + model + ", " + year+ "]"; } } public class FunctionTester { static Vehicle factory(Factory factoryObj, String make, String model, int year){ return factoryObj.prepare(make, model, year); } public static void main(String[] args) { //Constructor reference Factory vehicle_factory = Vehicle::new; Vehicle carHonda = factory(vehicle_factory, "Honda", "Civic", 2017); System.out.println(carHonda); } }
Output
Vehicle[Honda, Civic, 2017]
Functional Programming with Java – Collections
With Java 8 onwards, streams are introduced in Java and methods are added to collections to get a stream. Once a stream object is retrieved from a collection, we can apply various functional programming aspects like filtering, mapping, reducing etc. on collections. See the example below −
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class FunctionTester { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); //Mapping //get list of unique squares List<Integer> squaresList = numbers.stream().map( i -> i*i) .distinct().collect(Collectors.toList()); System.out.println(squaresList); //Filering //get list of non-empty strings List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); List<String> nonEmptyStrings = strings.stream() .filter(string -> !string.isEmpty()).collect(Collectors.toList()); System.out.println(nonEmptyStrings); //Reducing int sum = numbers.stream().reduce((num1, num2) -> num1 + num2).orElse(-1); System.out.println(sum); } }
Output
[9, 4, 49, 25] [abc, bc, efg, abcd, jkl] 25
Functional Programming – High Order Functions
A function is considered as a High Order function if it fulfils any one of the following conditions.
-
It takes one or more parameters as functions.
-
It returns a function after its execution.
Java 8 Collections.sort() method is an ideal example of a high order function. It accepts a comparing method as an argument. See the example below −
import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; public class FunctionTester { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(3, 4, 6, 7, 9); //Passing a function as lambda expression Collections.sort(numbers, (a,b) ->{ return a.compareTo(b); }); System.out.println(numbers); Comparator<Integer> comparator = (a,b) ->{ return a.compareTo(b); }; Comparator<Integer> reverseComparator = comparator.reversed(); //Passing a function Collections.sort(numbers, reverseComparator); System.out.println(numbers); } }
Output
[3, 4, 6, 7, 9] [9, 7, 6, 4, 3]
Functional Programming – Returning a Function
As a High Order function can return a function but how to implement using Java 8. Java 8 has provided Function interface which can accept a lambda expression. A high order function can return a lamdba expression and thus this high order function can be used to create any number of functions. See the example below −
import java.util.function.Function; public class FunctionTester { public static void main(String[] args) { Function<Integer, Integer> addOne = adder(1); Function<Integer, Integer> addTwo = adder(2); Function<Integer, Integer> addThree = adder(3); //result = 4 + 1 = 5 Integer result = addOne.apply(4); System.out.println(result); //result = 4 + 2 = 6 result = addTwo.apply(4); System.out.println(result); //result = 4 + 3 = 7 result = addThree.apply(4); System.out.println(result); } //adder - High Order Function //returns a function as lambda expression static Function<Integer, Integer> adder(Integer x){ return y -> y + x; } }
Output
5 6 7
Functional Programming – First Class Function
A function is called a first class function if it fulfills the following requirements.
-
It can be passed as a parameter to a function.
-
It can be returned from a function.
-
It can be assigned to a variable and then can be used later.
Java 8 supports functions as first class object using lambda expressions. A lambda expression is a function definition and can be assigned to a variable, can be passed as an argument and can be returned. See the example below −
@FunctionalInterface interface Calculator<X, Y> { public X compute(X a, Y b); } public class FunctionTester { public static void main(String[] args) { //Assign a function to a variable Calculator<Integer, Integer> calculator = (a,b) -> a * b; //call a function using function variable System.out.println(calculator.compute(2, 3)); //Pass the function as a parameter printResult(calculator, 2, 3); //Get the function as a return result Calculator<Integer, Integer> calculator1 = getCalculator(); System.out.println(calculator1.compute(2, 3)); } //Function as a parameter static void printResult(Calculator<Integer, Integer> calculator, Integer a, Integer b){ System.out.println(calculator.compute(a, b)); } //Function as return value static Calculator<Integer, Integer> getCalculator(){ Calculator<Integer, Integer> calculator = (a,b) -> a * b; return calculator; } }
Output
6 6 6
Functional Programming – Pure Function
A function is considered as Pure Function if it fulfils the following two conditions −
-
It always returns the same result for the given inputs and its results purely depends upon the inputs passed.
-
It has no side effects means it is not modifying any state of the caller entity.
Example- Pure Function
public class FunctionTester { public static void main(String[] args) { int result = sum(2,3); System.out.println(result); result = sum(2,3); System.out.println(result); } static int sum(int a, int b){ return a + b; } }
Output
5 5
Here sum() is a pure function as it always return 5 when passed 2 and 3 as parameters at different times and has no side effects.
Example- Impure Function
public class FunctionTester { private static double valueUsed = 0.0; public static void main(String[] args) { double result = randomSum(2.0,3.0); System.out.println(result); result = randomSum(2.0,3.0); System.out.println(result); } static double randomSum(double a, double b){ valueUsed = Math.random(); return valueUsed + a + b; } }
Output
5.919716721877799 5.4830887819586795
Here randomSum() is an impure function as it return different results when passed 2 and 3 as parameters at different times and modifies state of instance variable as well.
Functional Programming – Type Inference
Type inference is a technique by which a compiler automatically deduces the type of a parameter passed or of return type of a method. Java 8 onwards, Lambda expression uses type inference prominently.
See the example below for clarification on type inference.
Example- Type Inference
public class FunctionTester { public static void main(String[] args) { Join<Integer,Integer,Integer> sum = (a,b) -> a + b; System.out.println(sum.compute(10,20)); Join<String, String, String> concat = (a,b) -> a + b; System.out.println(concat.compute("Hello ","World!")); } interface Join<K,V,R>{ R compute(K k ,V v); } }
Output
30 Hello World!
A lambda expression treats each parameter and its return type as Object initially and then inferred the data type accordingly. In first case, the type inferred is Integer and in second case type inferred is String.
Exception Handling in Lambda Expressions
Lambda expressions are difficult to write when the function throws a checked expression. See the example below −
import java.net.URLEncoder; import java.util.Arrays; import java.util.stream.Collectors; public class FunctionTester { public static void main(String[] args) { String url = "www.google.com"; System.out.println(encodedAddress(url)); } public static String encodedAddress(String... address) { return Arrays.stream(address) .map(s -> URLEncoder.encode(s, "UTF-8")) .collect(Collectors.joining(",")); } }
The above code fails to compile because URLEncode.encode() throws UnsupportedEncodingException and cannot be thrown by encodeAddress() method.
One possible solution is to extract URLEncoder.encode() into a separate method and handle the exception there.
import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.Arrays; import java.util.stream.Collectors; public class FunctionTester { public static void main(String[] args) { String url = "www.google.com"; System.out.println(encodedAddress(url)); } public static String encodedString(String s) { try { URLEncoder.encode(s, "UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return s; } public static String encodedAddress(String... address) { return Arrays.stream(address) .map(s -> encodedString(s)) .collect(Collectors.joining(",")); } }
But above approach is not good when we have multiple such methods which throws exception. See the following generalized solution using functional interface and a wrapper method.
import java.net.URLEncoder; import java.util.Arrays; import java.util.function.Function; import java.util.stream.Collectors; public class FunctionTester { public static void main(String[] args) { String url = "www.google.com"; System.out.println(encodedAddress(url)); } public static String encodedAddress(String... address) { return Arrays.stream(address) .map(wrapper(s -> URLEncoder.encode(s, "UTF-8"))) .collect(Collectors.joining(",")); } private static <T, R, E extends Exception> Function<T, R> wrapper(FunctionWithThrows<T, R, E> fe) { return arg -> { try { return fe.apply(arg); } catch (Exception e) { throw new RuntimeException(e); } }; } } @FunctionalInterface interface FunctionWithThrows<T, R, E extends Exception> { R apply(T t) throws E; }
Output
www.google.com
Intermediate Methods
Stream API was introduced in Java 8 to facilitate functional programming in Java. A Stream API is targeted towards processing of collections of objects in functional way. By definition, a Stream is a Java component which can do internal iteration of its elements.
A Stream interface has terminal as well as non-terminal methods. Non-terminal methods are such operations which adds a listener to a stream. When a terminal method of stream is invoked, then internal iteration of stream elements get started and listener(s) attached to stream are getting called for each element and result is collected by the terminal method.
Such non-terminal methods are called Intermediate methods. The Intermediate method can only be invoked by called a terminal method. Following are some of the important Intermediate methods of Stream interface.
-
filter − Filters out non-required elements from a stream based on given criteria. This method accepts a predicate and apply it on each element. If predicate function return true, element is included in returned stream.
-
map − Maps each element of a stream to another item based on given criteria. This method accepts a function and apply it on each element. For example, to convert each String element in a stream to upper-case String element.
-
flatMap − This method can be used to maps each element of a stream to multiple items based on given criteria. This method is used when a complex object needs to be broken into simple objects. For example, to convert list of sentences to list of words.
-
distinct − Returns a stream of unique elements if duplicates are present.
-
limit − Returns a stream of limited elements where limit is specified by passing a number to limit method.
Example – Intermediate Methods
import java.util.Arrays; import java.util.List; import java.util.stream.Stream; public class FunctionTester { public static void main(String[] args) { List<String> stringList = Arrays.asList("One", "Two", "Three", "Four", "Five", "One"); System.out.println("Example - Filtern"); //Filter strings whose length are greater than 3. Stream<String> longStrings = stringList .stream() .filter( s -> {return s.length() > 3; }); //print strings longStrings.forEach(System.out::println); System.out.println("nExample - Mapn"); //map strings to UPPER case and print stringList .stream() .map( s -> s.toUpperCase()) .forEach(System.out::println); List<String> sentenceList = Arrays.asList("I am Mahesh.", "I love Java 8 Streams."); System.out.println("nExample - flatMapn"); //map strings to UPPER case and print sentenceList .stream() .flatMap( s -> { return (Stream<String>) Arrays.asList(s.split(" ")).stream(); }) .forEach(System.out::println); System.out.println("nExample - distinctn"); //map strings to UPPER case and print stringList .stream() .distinct() .forEach(System.out::println); System.out.println("nExample - limitn"); //map strings to UPPER case and print stringList .stream() .limit(2) .forEach(System.out::println); } }
Output
Example - Filter Three Four Five Example - Map ONE TWO THREE FOUR FIVE ONE Example - flatMap I am Mahesh. I love Java 8 Streams. Example - distinct One Two Three Four Five Example - limit One Two
Functional Programming – Terminal Methods
When a terminal method in invoked on a stream, iteration starts on stream and any other chained stream. Once the iteration is over then the result of terminal method is returned. A terminal method does not return a Stream thus once a terminal method is invoked over a stream then its chaining of non-terminal methods or intermediate methods stops/terminates.
Generally, terminal methods returns a single value and are invoked on each element of the stream. Following are some of the important terminal methods of Stream interface. Each terminal function takes a predicate function, initiates the iterations of elements, apply the predicate on each element.
-
anyMatch − If predicate returns true for any of the element, it returns true. If no element matches, false is returned.
-
allMatch − If predicate returns false for any of the element, it returns false. If all element matches, true is returned.
-
noneMatch − If no element matches, true is returned otherwise false is returned.
-
collect − each element is stored into the collection passed.
-
count − returns count of elements passed through intermediate methods.
-
findAny − returns Optional instance containing any element or empty instance is returned.
-
findFirst − returns first element under Optional instance. For empty stream, empty instance is returned.
-
forEach − apply the consumer function on each element. Used to print all elements of a stream.
-
min − returns the smallest element of the stream. Compares elements based on comparator predicate passed.
-
max − returns the largest element of the stream. Compares elements based on comparator predicate passed.
-
reduce − reduces all elements to a single element using the predicate passed.
-
toArray − returns arrays of elements of stream.
Example – Terminal Methods
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class FunctionTester { public static void main(String[] args) { List<String> stringList = Arrays.asList("One", "Two", "Three", "Four", "Five", "One"); System.out.println("Example - anyMatchn"); //anyMatch - check if Two is present? System.out.println("Two is present: " + stringList .stream() .anyMatch(s -> {return s.contains("Two");})); System.out.println("nExample - allMatchn"); //allMatch - check if length of each string is greater than 2. System.out.println("Length > 2: " + stringList .stream() .allMatch(s -> {return s.length() > 2;})); System.out.println("nExample - noneMatchn"); //noneMatch - check if length of each string is greater than 6. System.out.println("Length > 6: " + stringList .stream() .noneMatch(s -> {return s.length() > 6;})); System.out.println("nExample - collectn"); System.out.println("List: " + stringList .stream() .filter(s -> {return s.length() > 3;}) .collect(Collectors.toList())); System.out.println("nExample - countn"); System.out.println("Count: " + stringList .stream() .filter(s -> {return s.length() > 3;}) .count()); System.out.println("nExample - findAnyn"); System.out.println("findAny: " + stringList .stream() .findAny().get()); System.out.println("nExample - findFirstn"); System.out.println("findFirst: " + stringList .stream() .findFirst().get()); System.out.println("nExample - forEachn"); stringList .stream() .forEach(System.out::println); System.out.println("nExample - minn"); System.out.println("min: " + stringList .stream() .min((s1, s2) -> { return s1.compareTo(s2);})); System.out.println("nExample - maxn"); System.out.println("min: " + stringList .stream() .max((s1, s2) -> { return s1.compareTo(s2);})); System.out.println("nExample - reducen"); System.out.println("reduced: " + stringList .stream() .reduce((s1, s2) -> { return s1 + ", "+ s2;}) .get()); } }
Output
Example - anyMatch Two is present: true Example - allMatch Length > 2: true Example - noneMatch Length > 6: true Example - collect List: [Three, Four, Five] Example - count Count: 3 Example - findAny findAny: One Example - findFirst findFirst: One Example - forEach One Two Three Four Five One Example - min min: Optional[Five] Example - max min: Optional[Two] Example - reduce reduced: One, Two, Three, Four, Five, One
Functional Programming – Infinite Streams
Collections are in-memory data structure which have all the elements present in the collection and we have external iteration to iterate through collection whereas Stream is a fixed data structure where elements are computed on demand and a Stream has inbuilt iteration to iterate through each element. Following example shows how to create a Stream from an array.
int[] numbers = {1, 2, 3, 4}; IntStream numbersFromArray = Arrays.stream(numbers);
Above stream is of fixed size being built from an array of four numbers and will not return element after 4th element. But we can create a Stream using Stream.iterate() or Stream.generate() method which can have lamdba expression will pass to a Stream. Using lamdba expression, we can pass a condition which once fulfilled give the required elements. Consider the case, where we need a list of numbers which are multiple of 3.
Example – Infinite Stream
import java.util.stream.Stream; public class FunctionTester { public static void main(String[] args) { //create a stream of numbers which are multiple of 3 Stream<Integer> numbers = Stream.iterate(0, n -> n + 3); numbers .limit(10) .forEach(System.out::println); } }
Output
0 3 6 9 12 15 18 21 24 27
In order to operate on infinite stream, we”ve used limit() method of Stream interface to restrict the iteration of numbers when their count become 10.
Functional Programming – Fixed length Streams
There are multiple ways using which we can create fix length streams.
-
Using Stream.of() method
-
Using Collection.stream() method
-
Using Stream.builder() method
Following example shows all of the above ways to create a fix length stream.
Example – Fix Length Stream
import java.util.Arrays; import java.util.List; import java.util.stream.Stream; public class FunctionTester { public static void main(String[] args) { System.out.println("Stream.of():"); Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5); stream.forEach(System.out::println); System.out.println("Collection.stream():"); Integer[] numbers = {1, 2, 3, 4, 5}; List<Integer> list = Arrays.asList(numbers); list.stream().forEach(System.out::println); System.out.println("StreamBuilder.build():"); Stream.Builder<Integer> streamBuilder = Stream.builder(); streamBuilder.accept(1); streamBuilder.accept(2); streamBuilder.accept(3); streamBuilder.accept(4); streamBuilder.accept(5); streamBuilder.build().forEach(System.out::println); } }
Output
Stream.of(): 1 2 3 4 5 Collection.stream(): 1 2 3 4 5 StreamBuilder.build(): 1 2 3 4 5
”;