Functional programming¶
Introduction¶
Java is an object-oriented language that relies on creating objects and communicating between them. In this section, we will learn about the functional programming paradigm. Functional programming relies only on functions. The main program is the function we give the arguments and get the result in return. The main function of the program consists only of other functions, which in turn aggregate others.
SAM interfaces¶
Many interfaces in Java consist of only one abstract method, such as Runnable
,Callable
, Comparator
. Such interfaces are called Single Abstract Method
. Java 8 introduces the name FunctionalInterface
for this type of component. The Action
interface, defined below, is theSAM
interface:
public interface Action {
void execute(int x, int y);
}
The interface below cannot qualify as an interface of type SAM
because it has two abstract methods.
public interface Presenter {
void present(String text);
void present(String text, int size);
}
@FunctionalInterface¶
In order to make identification of functional interfaces easier, in Java there was introduced [annotation] (annotations.md) @ FunctionalInterface
, which informs the programmer that the indicated interface is supposed to be a functional interface. This annotation should be placed above the interface definition. Attempting to put this information over an interface that has, for example, zero or two abstract methods will result in a compile-time error.
Below we can see the correct use of the @FunctionalInterface
annotation:
@FunctionalInterface
public interface Executor {
void executor(int x);
}
NOTE: The functional interface does NOT need to be marked with the annotation
@FunctionalInterface
.
default¶
Java 8 introduces the default method mechanism, specified with the default
keyword. A default method is an interface method that has a default implementation (ie, this method has a body). Thanks to this new syntax, within the functional interface, we can declare more than one method, following the principle of a single abstract method, e.g .:
@FunctionalInterface
public interface Executor {
void executor(int x);
default void executor(int x, int y) {
// I have a body I am a default method
}
}
SAM
. Methods of the default
type can be overridden in implementing classes, or they can implement default functionality. Lambda expressions¶
The big problem with [anonymous] classes (anonymous_classes.md) is that even if we implement a simple interface with one method, the form of writing can be unreadable. An example of this is passing a function to another method, such as to handle the click of a button.
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Implementation of the Runnable interface as an implementation of an anonymous class!");
}
});
thread.start();
In Java 8, lambda expressions were introduced, that allow an anonymous class to be treated as a normal function, significantly shortening the syntax of the notation itself.
Thread thread = new Thread(() -> {
System.out.println("Runnable example using lambda!");
});
thread.start();
As you can see, the lambda expression exempts us from writing the new
keyword and using theOverride
annotation.
Syntax for the lambda expression¶
The lambda expression consists of three parts:
- list of arguments, which:
- must be in parentheses, if the number of arguments is different from 1
- may but doesn't have to provide argument types
- operator
->
, which is used after the list of arguments - the body of the implemented method, which follows the operator
->
- if the body consists of one expression then:
- this body does not have to be inside the braces, i.e.
{
i}
- in case this expression returns some object, we can omit the
return
keyword
- this body does not have to be inside the braces, i.e.
- if the body consists of many expressions, then:
- this body must be inside the braces, i.e.
{
i}
.
- this body must be inside the braces, i.e.
- if the body consists of one expression then:
NOTE: if lambda's task is to raise an exception, it has to be done inside the braces, even though it is a single expression.
NOTE: argument names may differ from those defined in the interface.
NOTE: defining a lambda we are completely not interested in the name of the abstract method in the interface.
Let's consider some examples of lambdas.
Example 1¶
For the following interface:
public interface Action {
String execute(int x, int y);
}
Lambda expression looks as follows:
- argument list:
(int x, int y)
- operator
->
- body of the implemented method:
{ return x + "-" + y; }
Całość prezentuje się w następujący sposób:
Action action = (int x, int y) -> {
return x + "-" + y;
};
Note that in the example above, we decided to add optional argument types, declare the body of the lambda inside the braces, and add the word return
. We can omit all these elements and reduce the notation to a single line:
Action action = (x, y) -> x + "-" + y;
Example 2¶
Let's consider the Runnable
interface:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
An example implementation could look like this:
Runnable runnableExample = () -> {
System.out.println("Hello from runnable");
System.out.println("{ and } cannot be omitted");
};
Note that in the absence of arguments, we must use ()
. The body of the lambda above consists of two statements, so it must be defined inside { }
.
Example 3¶
Another example would be to use the FruitEater
functional interface with the following definition:
@FunctionalInterface
public interface FruitEater<T> {
void consume(T t);
}
The following implementation has one input argument, so we can omit the parentheses by giving the argument list:
FruitEater<String> fruitEater = fruit -> System.out.println(String.format("eating %s... omnomnom", fruit);
Method references¶
Some simple lambda expressions we can write with method reference. Instead of writing a method call, we can only give its name. In this case, the name of the class and method must be separated by the characters '::'.
We can use a method reference when the lambda has one argument and one of the following conditions is met:
- lambda input argument is an argument of a method from some class
- an argumentless method is called on the input argument.
The following examples show possible uses of a method reference:
Consumer<String > consumerExample = someString -> System.out.println(someString); // use of lambda
Consumer<String > consumerExampleReference = System.out::println; // identical notation as in the line above, use of references
List.of("someString").stream().map(str -> str.toUpperCase()); // using lambda in the map method
List.of("someString").stream().map(String::toUpperCase); // using a reference to a method, equivalent notation in the line of code above
Functional interfaces in Java 8¶
Java 8 introduces many useful functional interfaces that can be used, among others in [Stream API] (stream_api.md). All the interfaces that will be discussed are in the java.util.function
package. The most commonly used are:
Supplier<T>
Function<T, R>
Consumer<T>
UnaryOperator<T, T>
Predicate<T>
Function¶
Function
is a generic function interface that takes an object of any type (T
) and returns an object of any type (R
). The apply
method is responsible for calling the implemented action. The following example shows the use of this interface:
public class FunctionExample {
public static void main(String[] args) {
Function<Employee, String> employeeToString = (employee) -> employee.getName(); // (1)
List<Employee> employees = Arrays.asList(new Employee("test"), new Employee("test2"));
showEmployee(employees, employeeToString);
}
static void showEmployee(List<Employee> employees, Function<Employee, String> showFunction) {
for (Employee employee : employees) {
System.out.println(showFunction.apply(employee));;
}
}
}
class Employee {
private String name;
public Employee(String name) {
this.name = name;
}
public String getName(){
return name;
}
}
In the above example, in line (1)
, we are creating an implementation of the Function
interface using a unary lambda that returns the value of thename
field in the Employee
object. Since this lambda consists of one expression, we don't have to write the return
keyword.
Supplier¶
The Supplier
is a generic functional interface which responsibility is to provide the value of an object of typeT
. The get
method returns a value implemented in the interface.
The following example uses a lambda expression to implement the Supplier
interface:
public class SupplierExample {
public static void main(String[] args) {
getValue(() -> "supplier test!");
}
static void getValue(Supplier<String> supplier){
System.out.println(supplier.get());
}
}
Consumer¶
Consumer
is another generic functional interface. Represents an operation that accepts a single input argument and returns no result. The generic type T
is an argument to the method. The interface is used when there is a need to "consume" the object. The accept
method is responsible for calling the implemented method, e.g .:
public class ConsumerExample {
public static void main(String[] args) {
Consumer<String> stringTrim = (s) -> {
s = s.trim();
System.out.println(s);
}; // Consumer implementation using multi-line lambda
trimValue(stringTrim, " text ");
}
static void trimValue(Consumer<String> trimAction, String s) {
trimAction.accept(s);
}
}
Predicate¶
The function interfaces Predicate
represents an operation that accepts a single argument and returns a logical value based on the parameter passed. So it is a specialization of the Function
interface. The generic type T
is the type of the method's input argument.
The test
method is responsible for returning the logical value of the test, e.g.
public class PredicateExample {
public static void main(String[] args) {
Predicate<Integer> predicate = (value) -> {
return value >= 0;
};
checkTest(predicate);
}
static void checkTest(Predicate<Integer> predicate) {
System.out.println(predicate.test(-1));
}
}
Optional¶
In Java, an object reference can either point to a real object or it can be empty, that is, null
. From a programmer's perspective, it is inconvenient to validate the reference each time. Therefore, we often carry out such validation, e.g. when:
- the documentation tells us about possible
null
values - relevant parts of the method are marked with annotation, e.g.
@Null
or@Nullable
Instead, we can use the mechanism introduced in Java 8. The Optional
in the packagejava.util
is an object that wraps the target, ie, it is some kind of box that such an object may or may not contain.
The Optional
class gives us access to many methods, including:
of
ofNullable
isPresent
ifPresent
orElse
orElseGet
Create an object of type Optional¶
The static method of
orofNullable
is responsible for creating objects. The difference between the first and the second method is, the fact that the first method does not allow taking a value of type null
.
The example shows both ways of creating described above:
public class CreatingOptionals {
public static void main(String[] args) {
final Optional<String> stringOptional = Optional.of("This is SDA!"); // creating a box with an object of the String type
String value = null;
if ((Integer.parseInt(args[0]) % 2 == 0)) {
value = "I am even";
}
final Optional<String> optionalThatCanBeEmpty = Optional.ofNullable(value); // use ofNullable since value can be null. In this case, Optional will be an empty box
}
}
Checking values¶
The Optional
class provides methods that allow you to perform an operation conditionally, if it actually contains a reference to an object.
- the
isPresent
method returnstrue
/false
depending on whether theOptional
holds any object - the
isEmpty
method returns a value opposite to the value returned by theisPresent
method, i.e. it gives information whetherOptional
does not store an object - the
ifPresent
method takes theConsumer
function interface as an argument. Thisconsumer
will only be called whenOptional
holds a reference to an object.
The following example shows the use of these methods:
public class OptionalsPresenceExample {
public static void main(String[] args) {
final Optional<String> optional = getStringForEvenNumber(3);
if (optional.isPresent()) {
System.out.println("I am optional with a value, I am non empty box");
} else if (optional.isEmpty()) { // warunek zawsze prawdziwy w tym momencie
System.out.println("I am an empty optional");
}
optional.ifPresent(System.out::println); // writing the values to a box on the screen, only if available
}
private static Optional<String> getStringForEvenNumber(final int number) {
if (number % 2 == 0) {
return Optional.of("even");
}
return Optional.empty();
}
}
Getting a value¶
The Optional
class provides several methods for getting a value:
- the
get
method returns the value stored in the object, or in the case of null it throws an exception:NoSuchElementException
- the
orElse
method returns the value stored inOptional
or the value indicated as the argument of the method in the case of an emptyOptional
- the
orElseGet
method returns the value stored inOptional
or the value indicated bySupplier
, which is an input argument.
public class OptionalOrElseExample {
public static void main(String[] args) {
String object = null;
String name = Optional.ofNullable(object).orElse("john");
System.out.println(name); // the value john will be printed on the screen
}
}
public class OptionalOrElseGetExample {
public static void main(String[] args) {
String object = null;
String name = Optional.ofNullable(object).orElseGet(() -> "john");
System.out.println(name); // String john reappears on the screen
}
}
NOTE: If we want to use the
get
method, we should first check the availability of an object using theisPresent
method.NOTE: The method
orElseGet
will only compute the replacement value as opposed toorElse
ifOptional
is empty. This means that the performance of theorElseGet
method is better than that of theorElse
method.