The new aspect of Java with functional programming

Shivanshu Goyal
Level Up Coding
Published in
5 min readAug 2, 2020

--

Functional programming is a well-known concept in Javascript and Python. But, it’s a fancy thing for a java developer. Prior to Java 8, Java has been a pure OOP where objects are first-class citizens. We used to do imperative coding in Java. From version 8 onwards, Java brings in the new capabilities to do functional programming. However, Java is not a functional programming language like Javascript.

What is functional programming?

In order to understand functional programming, we need to understand some basic concepts first.

  1. Imperative programming: In this programming paradigm, we need to tell what is required to be done and how can it be done? It relies on statements like if, when, for, forEach, etc.
public class Programming {

//remove duplicates from the list of numbers
public static void main(String[] args) {

List<Integer> numbers = Arrays.asList(1,2,2,3,4,5,6,7,7,8,9,9,8);

//imperative style of removing duplicates from the list
List<Integer> newList = new ArrayList<>();

for(Integer number: numbers) {
if(!newList.contains(number)) {
newList.add(number);
}
}
System.out.println(newList);
}
}

2. Declarative programming: It just requires what to do, it depends on expressions rather than statements. Declarative code focuses on building the logic of software without actually describing its flow.

public class Programming {

//remove duplicates from the list of numbers
public static void main(String[] args) {

List<Integer> numbers = Arrays.asList(1,2,2,3,4,5,6,7,7,8,9,9,8);

//declarative style of removing duplicates from the list
List<Integer> newList = numbers.stream().distinct().collect(Collectors.toList());

System.out.println(newList);
}
}

3. Higher-order functions: These are the functions that can take functions as arguments, and can return function after its execution also. Collections.sort() method is an example of a higher-order function that takes Comparator a parameter.

List<String> list = new ArrayList<>();
list.add("One");
list.add("Two");
list.add("Three");

Collections.sort(list, (String a, String b) -> {
return a.compareTo(b);
});

System.out.println(list);

4. Functional programming: It is declarative programming + higher-order functions + immutability. This programming is completely dependent on pure functions. Let’s understand what is a pure function in Java (or any programming language)?

A pure function is totally dependent on two things:

a. It does not change anything.

b. It does not depend on anything that changes. Like we can have only final variables in java lambdas.

//it's not a pure function as it modifies the list of numbers
public int getValue(List<Integer> numbers) {
numbers.add(100);
return numbers.get(0);
}

//it's a pure function as it does not modify the value of anything
public int getValue(List<Integer> numbers) {
return numbers.get(0);
}

Okay!! That’s really cool. Now, we understood what is this new jargon in Java. But, Why should we employ it in our implementation? How does it help us?

Why do we need functional programming?

  1. To have immutability in code.
  2. Parallelization is easier to achieve.
  3. Lazy evaluation

Why immutability is important?

Mutating data-structures can create unexpected side-effects, and we must check against them. With immutability, we can keep our data structures predictable and side-effect free, and easier to reason with.

In a pure function, we can use any data structure without worrying about mutability or vague results due to asynchronised access to the data structure.

Suppose, we have an imperative style code and it runs fine unless it’s a sequential code, The minute, we need to make it concurrent to improve its performance. It becomes complicated to do that. Okay, No problem, we love complicated things, we will do it, we start migrating our code from sequential to concurrent and we complete it, Now, we get faster results, but, results are not accurate. No problem, we’ll take some more time to fix those accuracy issues, Now, we are good to deploy our new solution with better performance.

Before we deploy our new solution, I have a question in mind, We used 10 threads in our concurrent code and it improved our performance by 5 times, Can’t we increase the number of threads more to have a better performance. Let’s try that. We increase the threads to 100 and the performance degrades this time. This experiment clearly tells us that concurrency is not dependent on threads only. It depends on the hardware (# of cores of CPU) too.

I am trying to explain that there are multiple things the developer needs to be cognizant about while working on concurrent code. Here, functional programming comes to rescue us from such problems by having abstraction on top of these things.

public class Programming {

//remove duplicates from the list of numbers
public static void main(String[] args) {

List<Integer> numbers = Arrays.asList(1,2,2,3,4,5,6,7,7,8,9,9,8);

//sequential code
List<Integer> newList = numbers.stream().distinct().collect(Collectors.toList());
//parallel code
List<Integer> newList = numbers.parallelStream().distinct().collect(Collectors.toList());
System.out.println(newList);
}
}

In the above example, parallelStream() executes the code in parallel to have better performance. It internally takes care of threads to divide the task into multiple sub-tasks.

Lazy evaluation

It does a lazy evaluation. It does not execute the flow if the result of the flow is not being used anywhere. Let me try to explain in the below example:

public class Programming {

public static int doubleNum(int number) {
System.out.println("doubleNumber: " + number);
return number*2;
}
//here result of stream is not being used
public static void main(String[] args) {

List<Integer> numbers = Arrays.asList(1,2,3,4);

numbers.stream().map(n -> doubleNum(n));
}

}
//Result:
Process finished with exit code 0
--------------------------------------------------------------------
public class Programming {

public static int doubleNum(int number) {
System.out.println("doubleNumber: " + number);
return number*2;
}

public static void main(String[] args) {

List<Integer> numbers = Arrays.asList(1,2,3,4);

numbers.stream().map(n -> doubleNum(n)).forEach(System.out::println);
}

}
//Result:
doubleNumber: 1
2
doubleNumber: 2
4
doubleNumber: 3
6
doubleNumber: 4
8
Process finished with exit code 0

Streams are the basic building block for functional programming in Java. It’s not a data-structure. It’s an abstraction of functions. It is based on a push model. As and when data is available to process, it pushes whereas, with the list and other collections, the client pulls the data to process.

Four famous functional interfaces:

  1. Supplier<T> =====> T get() ------> Factories in streams
  2. Predicate<T> =====> boolean test(T) ------> filters in streams
  3. Function<T, R> =====> R apply(T) -------> map in streams
  4. Consumer<T> ======> void accept(T) -------> forEach in streams

Note: Exceptions are not treated well in streams which becomes a limitation for streams. It just has a data channel to communicate. In an asynchronous programming, CompletableFuture comes in to handle errors (or exceptions) gracefully as it has 2 channels (data and error) to communicate.

Conclusion:

To my mind, Developers are more familiar with the imperative style of coding rather than the functional style (or declarative style) which requires more mathematical thinking and makes the code more error-prone. Why do we need to code like a mathematician? We should focus on business logic only and leverage the APIs and frameworks provided to us to achieve other things.

Hope it helps, Thanks for reading!

--

--

Software Engineer @Salesforce, India | Ex-Walmart | Ex-Motorola | Ex-Comviva | Ex-Samsung | IIT Dhanbad