What is functional programming
Let’s start by looking at some examples of programming paradigms.
Imperative programming
The program is seen as a detailed sequence of instructions to be executed. The developer must instruct the program on what to do and how to do it.
Declarative programming
More high-level approach than imperative programming in which the program is viewed as a series of less detailed instructions. In this approach, the developer specifies what is to be done but places no constraints on how it is to be done.
Functional programming
Programming paradigm that sees functions as fundamental elements, on a par with objects and variables. In functional programming, functions can be defined (anonymous or not) that can be passed as arguments to other functions or you can have functions that return other functions as results.
Object-oriented programming
Object Oriented Programming (OOP), based on the concept of an object, seen as the basic entity for representing information, with its own state and behaviors.
Event-oriented programming
Paradigm whereby software behavior is determined by the occurrence of certain events, rather than by a precise sequence of instructions.
Basic constructs of functional programming
Let us now introduce some basic functional programming constructs, such as lambda expressions, functional interfaces, Java Stream API, and Method reference
Lambda expressions
What are they? How do they work? Where are they used?
Lambda expressions are constructs introduced in Java starting with version 8 that provide a way to express instances of functional interfaces and write more concise code.
Lambda expressions generally involve:
(x, y) -> {
instruction1
…
return z;
}
The argument list can also be empty, and in case the function only needs to return a value, the return statement and staples can be omitted.
() -> z
Best practices and advice
PRO (if used correctly)
AGAINST (if used improperly).
Functional interfaces
What are they, how do they work, where are they used, and which ones are most commonly used?
Functional interfaces were introduced in Java 8 to support functional programming. A functional interface is defined as an interface that contains the declaration of a single abstract method.
Other static or default methods may also be present in the interface, but it is important that there is only one abstract method.
With functional interfaces, lambda expressions and method referencing can be used more efficiently.
Consumer
Java’s Consumer interface is a functional interface that represents an operation with an object as input but no result as output.
x -> {
instruction1
…
instructionN
}
Consumers are used to perform actions on or from the object received as input.
The Consumer interface provides an accept() method that, when called, allows the actions contained in the Consumer’s own instruction block, defined by means of a lambda expression, to take place.
We then define a Consumer variable
The behavior of the Consumer is described by a lambda expression in which the input value is the object contained in the Consumer, an integer x, and the instruction body contains the actions to be performed by the Consumer based on the input received.
In this case, the Consumer prints first the integer value received as input and then its square.
When the Consumer is actually to be used, with the operations defined in it, it will be necessary to call the accept() method by passing to it as input the object to be used by the Consumer.
There are some specializations of the Consumer interface that allow certain types of objects to be handled implicitly.
With these Consumer specializations, it is no longer necessary to specify the type of the generic in angle brackets.
There are also BiConsumers, which are Consumers that accept two objects as input and return nothing as output.
BiConsumer
Supplier
Java’s Supplier interface is a functional interface that represents an operation that does not involve an input object but returns an output object.
() -> {
instruction1
…
instructionN
return x;
}
Or
() -> x
Suppliers are used to provide an instance of an output object.
The Supplier interface provides a get() method that, when called, allows the Supplier result object to be obtained.
As with other functional interfaces, Supplier’s behavior is defined by a lambda expression.
We then define a Supplier variable
The Supplier needs no input objects, so we have empty parentheses, and it returns as output a causal integer computed with Java’s Math.random() method.
When it becomes necessary to actually use the Supplier, the get() method must be called, which returns the result of the lambda expression.
Preach
Java’s Predicate interface is a functional interface for evaluating a more or less complex condition, given an object as input, and returning a Boolean value as output.
x -> {
instruction1
…
instructionN
return true/false;
}
or
x -> true/false
Predicates receive as input an object and, based on this object, evaluate a condition specified in the lambda expression and return the boolean result of this condition.
The Predicate interface provides a test() method that, when called, returns true or false depending on whether the condition is met or not.
As with other functional interfaces, the behavior of the Predicate is defined by a lambda expression.
A Predicate variable
Predicate needs an object as input and returns as output a boolean, the result of the evaluated condition. The lambda starts with an integer x and evaluates whether it is greater than 10, then returns the result.
When it becomes necessary to actually use the Predicate, the test()method must be called by passing it the Predicate object as input, which returns the result of the lambda expression.
There are some specializations of the Predicate interface that allow certain types of objects to be handled implicitly.
With these specializations of Predicate, it is no longer necessary to specify the type of the generic in angle brackets.
There are also BiPredicates, which are Predicates that accept two objects as input and return nothing in output.
BiPredicate
Function
Java’s Function interface is a functional interface that allows you to define a generic function that, given one object as input, returns another as output
x -> {
instruction1
…
instructionN
return y;
}
Or
x -> y
Functions receive as input an object, perform certain operations, and output another object as the result.
The Function interface provides an apply() method that, when called, performs the actions defined by the lambda expression and returns another object in output.
As with other functional interfaces, the behavior of Functions is defined by a lambda expression.
A variable Function
Function needs an object as input and returns another object as output. The lambda starts with a string s and returns the string converted to lowercase characters.
When it becomes necessary to actually use the Function, you must call the apply()method by passing it as an argument the object to be processed by the Function, which returns the result of the lambda expression.
There are some specializations of the Function interface that allow certain types of objects to be handled implicitly:
BiFunction
A BiFunction is a specialization of the Function functional interface that provides two objects as input and returns a third object as output.
BiFunction
instruction1
….
instructionN
return s;
}
UnaryOperator
Java’s UnaryOperator interface is a functional interface that is a specialization of Function and allows one to define a generic function that, given one object as input, returns another object as output with the special feature that input and output must have the same type (so only one generic needs to be specified at declaration time).
UnaryOperator
…
return y;
}
Similar to other functional interfaces (e.g., Predicate), there are additional specializations for UnaryOperators that allow them to implicitly handle certain types of values.
‘UnaryOperator is defined by means of a lambda that, taken as input the integer, returns its value multiplied by 100.
When the UnaryOperator is to be used, the apply()method must be called on it by passing an integer value as input, which will be processed by the lambda
BinaryOperator
Java’s BinaryOperator interface is a functional interface that is a specialization of Function and allows you to define a generic function that, given two objects as input, returns a third object as output with the special feature that input and output must have the same type (so only one generic needs to be specified at declaration time).
BinaryOperator
…
return z;
}
Similar to UnaryOperators, there are additional specializations for BinaryOperators that allow them to handle certain types of values implicitly.
The BinaryOperator is defined by means of a lambda that, taken as input two integers, returns their sum (also of type integer).
When the BinaryOperator is to be used, the apply()method must be called on it by passing it as input two integer values, which will be processed by the lambda.
Java Stream API
What are they, how do they work, where are they used, and what are the most commonly used methods?
The Java Stream APIs are functions introduced starting with Java version 8 that make available a number of high-level operations to perform divide-type operations on aggregations of data (e.g., collections) including:
Starting with an aggregation of elements (such as can be a Map, List, Set, or any other form of Collection instance), we open a Stream, which is an object representing the sequence of elements in the Collection on which we want to operate.
ForEach
The forEach() method of the Java Stream API allows you to perform a certain action on each element of a Stream.
The method requires as an argument a Consumer describing the actions to be performed on each Stream element.
After defining a list of strings, you open a Stream on it using the stream() method after which you call on the Stream the forEach() method passing to it as an argument a Consumer defined by means of a lambda.
That lambda, given each element s of the Stream, prints it out.
Filter and collect
The filter() method of the Java Stream API, given a starting Stream, makes it possible to generate a second Stream containing all the elements of the first one that satisfy a certain condition, expressed by means of a Predicate.
After opening a Stream on a list of strings, we call the filter() method on it by passing it as an argument a Predicate with the condition to be verified, expressed in this case by means of a lambda.
To convert the Stream returned by filter() to a List, you can use si of it the collect(Collectors.toList()) method.
Map
The map() method of the Java Stream API allows a Function to be applied to each element of a Stream. That function is used to generate a second Stream containing the elements resulting from the application of that function.
After opening a Stream on a list of strings, you call the map() method on it by passing it a Function as an argument, expressed in this case by means of a lambda.
Such a Function takes as input each string from the source Stream and returns as output its length. The resulting Stream will then be a Stream of Integer.
Thus we see that map() can also be used to “transform” the type of objects that sista considering in the Stream.
Count and distinct
The count() method of the Java Stream API returns the number of elements in a Stream.
The distinct() method, on the other hand, given a Stream, allows a second one to be generated containing the elements of the first one without duplicates.
After opening a Stream on a list of strings, you call the distinct() method on it to generate a second Stream containing only the elements of the first one without duplicates.
After that, count() is called, which returns the number of Stream elements generated by distinct() in the form of a numeric value long
Sorted
he sorted() method of the Java Stream API allows you to generate a Stream containing the elements of the Stream on which it was called sorted according to a specific criterion.
After opening a Stream on a list of strings, you call the sorted() method on it. passing it as an argument a Comparator indicating the type of sorting that is to be applied to the elements of the Stream.
If no Comparator is specified, sorted() will apply natural sorting based on the type of elements being processed, provided there is one.
You then get a secondStream containing the elements of the starting one sorted according to the established criterion.
AnyMatch, allMatch and noneMatch
he anyMatch() method of the Java Stream API allows you to verify that at least one element of theStream satisfies a certain condition, expressed by means of a Predicate.
Similarly, allMatch() checks whether all the elements in the Stream satisfy a certain condition, while noneMatch() checks that none of them satisfy it.
After opening a Stream on a list of strings, you call the anyMatch() method on it by passing it as an argument a Predicate describing the condition to be tested. The method returns true/false depending on the outcome of the evaluation.
Since there is at least one string in the stream that begins with M, the method returns true.
The allMatch() and noneMatch() methods work quite similarly.
Method reference
Method reference refers to a feature of Java introduced starting with version 8 that allows methods to be referred to (under certain circumstances) with a more concise syntax than even lambda expressions.
The method reference is often used in conjunction with the Java Stream API in place of lambda expressions.
Suppose we have a list of strings and we want to print them all.
The name of the class is then given followed by the reference operator :: and the name of the method to be used, without specifying the arguments.
NameClass::nameMethod
The result can be interpreted in this way:
It should be noted that when using the method reference instead of a lambda, care must be taken to ensure that the input type of the lambda is compatible in number and type with the parameter list of the method reference.
In other words, again exploiting the previous example, we have that the lambda takes as input a string s given by forEach(), which is consistent with the list of parameters that you can pass to System.out.println().
Therefore, the lambda is replaceable with the method reference.