Java Functional Programming

Why?

  • Functional Programming is a programming paradigm for developing and solving software problems in terms of functions and immutable values that translate between them.

  • This means that these functions and values have:

    • no state

    • no side effects

    • are immutable variables

    • favour recursion (iteration?) over looping

public class ObjectWithPureFunction{

  public int sum(int a, int b) {
    return a + b;
  }
}

The example given above is of a pure function, whose output sum() only depends on its input parameters.

How?

  • Functional Programming facilitates the reuse of common utility functions to process data more efficiently.

  • It takes advantage of the ability to represent a function as a value that can be put into a variable.

  • This is unlike Object Oriented Programming, where methods and data are together. Here, we’re only interested in the function / method. Which is also the reason why we define the method with a functional interface, because an interface focuses on a method interface and not its implementation or state.

  • These characteristics are helpful for creating common utility functions (like macros) that can act on many different data types.

  • Because it has no state, no side effects and use immutable variables, we can create high order functions that take other such functions as arguments, or return functions.

  • High Order Functions give us the possibility of chaining such utility functions together, allowing us to write succinct code that gets us to the heart of what we want to do.

  • For this purpose, Lambda Expressions were introduced in Java 8. Lambda Expressions are used to pass a block of code to another method or object.

  • It greatly simplifies the syntax used to define inner classes and anonymous classes, making it more natural for developers to use functional programming instead.

  • Functional Interfaces serve as a data type for Lambda Expressions, because a functional interface contains only one abstract method, removing the ambiguity about which method would be used.

Examples (What?)

Given below are some very basic operations commonly used in functional programming:

collect()

This is a terminal method with an eager operation that generates a list from values in a stream. It provides options for packaging, such as concatenation, toList, toMap, toSet, etc.

1List<String> collected = Stream.of("a", "b", "c")
2                            .collect(Collectors.toList());
3assertEquals(Arrays.asList("a", "b", "c"), collected);
  • Line 1 starts by getting a Stream from a List.

  • Line 2 creates a new list.

  • Line 3 asserts that the lists are equal (have the same content).

1String[] input = {"tiger", "cat", "TIGER", "Tiger", "leopard"};
2List<String> cats = Arrays.asList(input);
3String search = "tiger";
4String tigers = cats.stream()
5                  .filter(s -> s.equalsIgnoreCase(search))
6                  .collect(Collectors.joining(", "));
7System.out.println(tigers);

Some of the collectors avalable are toList(), toSet(), toMap(), collectingAndThen(), joining(), counting(), summarizingDouble/Long/Int(), averagingDouble/Long/Int(), summingDouble/Long/Int(), maxBy(), minBy(), groupingBy(), partitioningBy() and teeing().

map()

This function processes each element in a list or list-like container, performs an operation on each element, and creates a new list.

1List<String> collected = new ArrayList<>();
2for (String theString : asList("a", "b", "hello")) {
3  String upperCaseString = theString.toUpperCase();
4  collected.add(upperCaseString);
5}
6assertEquals(asList("A", "B", "HELLO"), collected);

filter()

This function processes each element, eliminating elements that do not meet a user defined selection criteria and creates a new (smaller) list.

long count = allArtists.stream().filter(artists -> {
  return artist.isFrom("London");
})

reduce()

This function usually takes two parameters - an initial value and an aggregation or reduction function (such as sum()), and applies that function to each element in turn, building up a final result from the whole list or collection.

int sum = Stream.of(1,2,3).reduce(10, (acc, element) -> acc + element);
assertEquals(16, sum);

foreach()

List<String> pets = Arrays.asList("dog", "cat", "fish", "iguana", "ferret");
pets.stream().forEach(System.out::println);

Note

  • From the examples above, it can be seen that, in functional programming, it is usual to create a new list or collection, rather than modifying an existing one in-place.

  • With collections, the The Java Streams API abstraction is what really brings out the benefits of functional programming.

  • Java collections are imperative and not functional. Hence, they need to be converted to streams before functional programming can be used.

  • With The Java Streams API, Java brings about Functional Programming as another tool for developers.

Lexical Scoping & Class Instance from an Interface

 1public class sol_101a {
 2    public interface IntHolder {
 3        int getValue();
 4    }
 5
 6    public static void Solution101() {
 7        System.out.println("Solution101 --> Entered function.");
 8        // Java allows us to create an IntHolder class from an interface
 9        IntHolder[] holders = new IntHolder[10];
10
11        for (int i = 0; i < holders.length; i++) {
12            final int fi = i + 100;
13            // this statement below will not work
14            // holders[i] = fi;
15            holders[i] = () -> fi;
16        }
17
18        for (IntHolder i:holders) {
19            System.out.println("sol_101a::Solution101 --> holders[] = " + i.getValue());
20        }
21    }
22}

The example above shows a few things based on Functional Programming:

  • The ability to create an instance of a class from an interface, without having to use the “implements” keyword.

  • The local temporary variable fi (line 12), is technically confined in scope to its curly brackets in the for loop. However, the lambda expression in line 15 has the ability to break this assumption, because the lambda expression makes a copy of fi, which is what lambda expressions usually do.

The output for the code shown above will result in: .. code-block:

sol_101a::Solution101 --> holders[] = 100
sol_101a::Solution101 --> holders[] = 101
sol_101a::Solution101 --> holders[] = 102
sol_101a::Solution101 --> holders[] = 103
sol_101a::Solution101 --> holders[] = 104
sol_101a::Solution101 --> holders[] = 105
sol_101a::Solution101 --> holders[] = 106
sol_101a::Solution101 --> holders[] = 107
sol_101a::Solution101 --> holders[] = 108
sol_101a::Solution101 --> holders[] = 109