Blog

Going a little functional with Java

Author Image

Leonel da Silva

Full-stack Java Developer

11 apr 2018

Header image

Since Java 8, the language decided to incorporate some functional features in itself, which help us to have a little better code if we understand the concepts. Functional languages have been successfully used for decades, and they usually present a more elegant way of expressing program logic. Functional languages such as Lisp, Closure, Haskell or Elixir can present some concepts and techniques that can be odd or even hard to be understood by imperative languages developers.

Functional languages concepts

First of all, it is not the main purpose of this article to explain every single concepts of functional programming or even stress all Java 8 new features, but it is the purpose to give a few tips to improve your java code with functional solutions and new java features.

In order to list some major aspects of functional programming, the list are bellow:

  • Functions and function composition
  • Fluent interfaces
  • Strict and non-strict evaluation
  • Persistent data structures, monads, and the Optional class
  • Recursion and parallelism

To start to talk about how this paradigm can help you, I’d like to talk a little about functions:

Functions

They play a central role in supporting other functional programming concepts. In functional programming, we need to introduce many of the terms used to describe functions including high-order, first-class, and pure functions.

First-class functions are functions that can be used in any place that a first-class entity is used, first-class entities include numbers and string for example.

Higher-order functions are functions that can receive a function as a parameter and return a function as a result as well.

Pure functions are functions that don’t produce side-effects. This means that memory external to the function is not modified, IO is not performed, and no exceptions are thrown. With a pure function, when it is called repeatedly with the same parameters, it will return the same value. This is called referential transparency.

Pure functions are one of the concepts that most contribute to have a clean and better code. Advantages of pure function include:

  • The function can be called repeatedly with the same argument and get the same results. This enables caching optimization (memorization).
  • With no dependencies between multiple pure functions, they can be reordered and performed in parallel. They are essentially thread safe.
  • Pure function enables lazy evaluation as discussed later in the Strict versus non-strict evaluation section. This implies that the execution of the function can be delayed and its results can be cached potentially improving the performance of a program.
  • If the result of a function is not used, then it can be removed since it does not affect other operations.

The main concept to keep you code cleaner and less proned to error is adopting the pure functions concept. In the functional paradigm, applications are constructed by using pure functions as much as possible.
A pure function is a function which does not have side effects. A side effect occurs when a function does something else besides simply returning a value, such as mutating a global variable or performing IO.

A little (non-functional) example about how side effects can be annoying:

private static void sideEffectTest() {

                               Integer[] x = {2};

                               System.out.println(" without side effects " );
                               Integer x1 = withoutSideEffect(x);
                               System.out.println("Original value " + x[0]);
                               System.out.println("Result value " + x1);
                               Integer x2 = withoutSideEffect(x);
                               System.out.println("Original value " + x[0]);
                               System.out.println("Result value " + x2);

                               Integer x3 = withoutSideEffect(x);
                               System.out.println("Original value "+x[0]);
                               System.out.println("Result value " + x3);

                               System.out.println("Now with side effects");

                               sideEffect(x);
                               System.out.println(x[0]);
                               sideEffect(x);
                               System.out.println(x[0]);
                               sideEffect(x);
                               System.out.println(x[0]);

                }

               

                private static Integer sideEffect(Integer[] x) {
                               x[0] = x[0]*2;
                               return x[0];
                }

                private static Integer withoutSideEffect(Integer[] x) {
                               Integer aux = x[0];
                               aux *=2;
                               return aux;
                }

The result of this execution is:

Without side effects
Original value 2
Result value 4
Original value 2
Result value 4
Original value 2
Result value 4
Now with side effects
4
8
16

Here you can think it is not a great problem, but most of the time side effects will happen with complex objects and you are going to lose information in business logic method, and it takes hours to find out where the value was unproperly changed.

Then, one of the benefits to have a function that always return the same result for the same input is having memoization (caching already calculated values)

Simple approach:

                private static Integer doComputeExpensiveSquare(Integer input) {
                               System.out.println("Computing square");
                               return 2 * input;
                }

                private final Map<Integer, Integer> memoizationCache = new HashMap<>();

                public Integer computeExpensiveSquare(Integer input) {
                               if (!memoizationCache.containsKey(input)) {
                                               memoizationCache.put(input, doComputeExpensiveSquare(input));
                               }
                               return memoizationCache.get(input);
                }

This approach is good for one method, but if you need to use it for different kinds of data, you will need to declare many maps to hold those information. The functional approach here will help a lot in this problem. First you create a new class to handle this problem:

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

public class Memoizer<T, U> {

                private final Map<T, U> memoizationCache = new ConcurrentHashMap<>();

                private Function<T, U> doMemoize(final Function<T, U> function) {
                               return input -> memoizationCache.computeIfAbsent(input, function::apply);
                }

                public static <T, U> Function<T, U> memoize(final Function<T, U> function) {
                               return new Memoizer<T, U>().doMemoize(function);
                }
}

Then after you can create a lambda function to be used as parameter.

                               Function<Integer, Integer> squareFunction = x -> {
                                               System.out.println("In function");
                                               return x * x;
                                               };

Then you create a instance of this Memoizer class using the function you want to execute memoized (pay attention that it could be any function)

                               Function<Integer, Integer> memoizationFunction = Memoizer.memoize(squareFunction);                              

                               System.out.println(memoizationFunction.apply(2));
                               System.out.println(memoizationFunction.apply(2));
                               System.out.println(memoizationFunction.apply(2));

The other benefits to use pure functions, like lazy evaluating will be discussed when I present you the Stream concept.

Fluent interfaces

This is a concept very common in JS ECMAScript6 or later versions, it helps you to read what a command is doing more similar to a natural language. A good example of how fluent interface make   easier to read a command is the new Date library from Java 8, LocalDate:

                               LocalDate date = LocalDate
                                                               .now(); //what day?
                               System.out.println(date.toString());
                               //Today is March 27th 2018.
                               date.minusMonths(3);
                               System.out.println(date.toString());
                               //See that is a pure function so the in parameter never change it's own value

                               LocalDate threeMonthsAgo = date.minusMonths(3);
                               System.out.println(threeMonthsAgo.toString());

                               //Now some date and time
                               LocalDateTime myBirthDateAndTime = LocalDate
                                                                              .of(1983, 10, 06) //what day?
                                                                              .atTime(LocalTime.of(8, 45)); //what time
                               System.out.println(myBirthDateAndTime.toString());

                               //Not related to that but remember you never should use LocalDate or LocalDateTime
                               //as  Model types but you need to convert them to java.sql.Date as bellow
                               Date sqlDate = Date.valueOf(date);
                               Timestamp sqlTimestamp = Timestamp.valueOf(myBirthDateAndTime);

Stream

Now we are going to talk about on really important change in Functional paradigm: Avoiding use of loops

But what is wrong with loops?

Loops in any language is the best example of imperative language. We say it is a imperative language when we tall the machine how to do something, and it is frequently compared with declarative approach that is tell the machine what to do. This is a very important concept when we talk about functional languages:

To better explain it might be useful to compare declarative approach versus imperative approach. Imperative approach is when you command the programm language to do something, it is closer to the machine language than a natural language.

Given an array with company names like below:

String[] arr = {"ABN","ING","Coca-cola","Yacht","Facebook","Google","Yahoo","Apple"};

For example to order a array using imperative approach (I’m sorting manualy to make a point):

                               //imperative way
                               int n = arr.length;
                               for (int i = 0; i < n-1; i++)
                for (int j = 0; j < n-i-1; j++)
//Remember that use bubble sort to sort a string array is a terrible idea
//The O(n) complexity will be O(n^3) = O(n^2) for the sorting and O(n) to compare strings
                       if (arr[j].compareTo(arr[j+1]) > 0 )
                       {
                           // swap temp and arr[i]
                           String temp = arr[j];
                           arr[j] = arr[j+1];
                           arr[j+1] = temp;
                       }

                               for (int i = 0; i < arr.length; i++)
                                               System.out.println(arr[i]);

The solution using a declarative approach will as bellow:

                               //declarative way
                               List<String> listFromArray = Arrays.asList(arr);
                               //solving the problem using stream
                               listFromArray.stream()
                    .sorted()
                    .forEach(System.out::println);

This approach is closer to the natural language, you are telling to the list to be sorted then asking to each term to print itself.

In this second example we have used the Streams, which is one of more powerful features Java 8 added in this API. A stream can be thought of as a sequence of elements processed by a series of methods using a fluent interface. The stream concept is supported by the Stream class.

How stream methods are evaluated is also of interest—either in a lazy or eager manner. How they are evaluated affects when their methods are executed. The uses and advantages of each approach are explained.

One of the useful features of streams is how they support concurrent behavior. Streams are executed concurrently using the parallel method. While this is easy to achieve, care must be taken use it correctly.

Streams helps us to solve some problems  easily, lets think we have a array of number and we want to get all distinct number and sum them to return the total of these distinct numbers:

                               int[] numbers = {3,6,8,8,4,6,3,3,5,6,9,4,3,6};

                               //to make them distinct I'll use a Set to help me.
                               Set<Integer> distinctSet = new HashSet<>();                              

                               for (int number : numbers) {
                                               distinctSet.add(number);
                               }
                               Integer total = 0;
                               for (Integer dn : distinctSet) {
                                               total += dn;
                               }
                              System.out.println(total);
                               //now using streams
                               Integer total2 = Arrays.stream(numbers).distinct().sum();
                               System.out.println(total2);

This is also a good example of how fluent interfaces are better to read. To sum up, streams allows you to realize filtering, maping, reducing and other many transformations in the streams, concurrently, taking more advantage of the multi-thread aspect of the new processors.

Lazy evaluation

Another interesting feature of Functional Java is the lazy evaluation. The evaluation will happen only when needed, take a look in the example below:

                               Function<Integer,Integer> divide = n->1/0;
                               Function<Integer,Integer> add = n->n+3;
                               Function<Integer,Integer> multiply = n->n*5;
                               Function<Integer,Integer> subtract = n->n-4;                              

                              

                               Function[] arr = {divide,add,multiply,subtract}
                               Stream<Function> stream = Arrays.stream(arr);

                               //divide would cause a division by zero exception, but it was skipped. --> Java.lang.ArithmeticException: / by zero
                               stream
                                               .skip(1)
                                               .forEach(operation->System.out.println(operation.apply(2)));

And another example can be seen below:

                               //in this operation we desire to show you that sorting only happen when forEach force it
                               // to be evaluated.
                               IntUnaryOperator sampleMap = num -> {
                                               System.out.println("number: " + num);
                                               return num;
                               };

                               Random random = new Random();
                               IntStream randomStream = random.ints()
                                                               .limit(5)
                                                               .map(sampleMap)
                                                               .sorted();                              

                               System.out.println(randomStream);                              

                               randomStream.forEach(System.out::println);

Optional

Then, to finalize this article I’ll talk a little about Optional, one of the new classes Java 8 has implemented. It is a class that wrap the returning objetcs providing a more declarative way to check if the value is empty or not. It will provide more elegant ways this situation that the common imperative: if (object==null). Here code example.

                public Customer findCustomerWithID(int id) {
                               if (customers.containsKey(id)) {
                                               return customers.get(id);
                               } else {
                                               return null;
                               }
                }

                public Optional<Customer> findOptionalCustomerWithID(int id) {
                               if (customers.containsKey(id)) {
                                               return Optional.of(customers.get(id));
                               } else {
                                               return Optional.empty();
                               }
                }

Above you can see two different implementations of a findById. Now compare two the ways:

                               // you must treat null value
                               if (customer != null) {
                                               if (customer.getName().equals("Mary")) {
                                                               System.out.println("Processing Mary");
                                               } else {
                                                               System.out.println(customer);
                                               }
                               } else {
                                               System.out.println(defaultCustomer);
                               }
                               // using optional
                               Optional<Customer> optionalCustomer = customers.findOptionalCustomerWithID(id);
                               if (optionalCustomer.isPresent()) {
                                               if (optionalCustomer.get().getName().equals("Mary")) {
                                                               System.out.println("Processing Mary");
                                               } else {
                                                               System.out.println(optionalCustomer.get());
                                               }
                               } else {
                                               System.out.println(defaultCustomer);
                               }

To make it more functional you can also pass a function in the ifPresent method of the optional:

                               Consumer<Customer> consume = o -> {
                                               if (o.getName().equals("Mary")) {
                                                               System.out.println("Processing Mary");
                                               } else {
                                                               System.out.println(optionalCustomer.get());
                                               }
                               };
                               Optional<Customer> optionalCustomer2 = customers.findOptionalCustomerWithID(789);
                               optionalCustomer2.ifPresent(consume);                              

                               if (!optionalCustomer2.isPresent()) {
                                               System.out.println(defaultCustomer);
                               }

More features and examples

Those are a few examples of how to make your code more functional in Java and also have more elegant solutions. You can see those codes and other I’ll continue to do in my github:

https://github.com/leonelcs/FunctionalJava8

Thanks for reading.

Leonel

Gerelateerde artikelen & blogs