Transition to Functional Programming for Java Developers

In this article, we will cover Functional Programming from a Java language perspective. We will see how the Functional Programming style has been introduced starting from Java 8.

Figure: Functional Programming in Java

Introduction

Recent years we often hear the term Functional Programming in IT conferences or co-working space meetups. For developers who come from a solid computer science background, this is not new at all. In fact, it is the subset of two programming paradigms: imperative and declarative.

Figure: Programming language paradigms 

It is important to understand differences between imperative and declarative programming to see why Functional Programming is now being widely adopted. Here is a quick summary:

Characteristic Imperative Declarative 
Programming Style Focus on How: perform step-by-step tasks and manage changes in state Focus on What: define what the problem is and what data transformation is needed to achieve the solution 
State Changes Stateful Stateless 
Order of execution Statements are executed in a specific order Statements can be executed in any order 
Flow control Loops, conditionals, and function calls Function call and Recursion 
Element Class objects and methods Functions, data sets 

What is Functional Programming? 

In Object Oriented Programming (OOP), the building block is a class, and every class extends from the Object class. We encapsulate the states and behaviors of real-world matter inside a class as attributes and methods. On the other hand, in the most simplified definition, Functional Programming focusses on the function as the building block. Strictly speaking, fully supported Functional Programming must allow certain design patterns:

  • Inferred type system
  • Support anonymous function
  • Implement Lambda calculus
  • Deferred execution
  • Immutable data structures
  • Non-strict, lazy evaluation

We will see more details of how Java supports these features later in this post.

If we follow the strict definition of Functional Programming, then Java (being a strong object oriented language) is not a Functional Programming language. However, we still can be benefit from writing effective functional-style programs in Java.

From version 8 onward, Java has gradually added Functional Programming features to its capabilities. Starting with Lambda, there is a new style of using lambda expressions with Collection APIs and data processing with the new Stream API. In Java 10, Oracle introduced a long requested functional feature: “inferred type”.

Why Functional Programming became famous again? 

With improvement in compilers and increasing demand for stateless programming models, software engineers have recently re-visited Functional Programming and invested more into it. In addition, the painful experience of maintaining multi-threaded OOP programs also pushes software engineers toward Functional Programming.

To demonstrate the popularity of Functional Programming in recent years, we can refer to the top 10 trending programming languages by IEEE:

screenshot of the the top ten list from the app
Trending programming languages (IEEE, 2019) 

Here we visit some of the languages in the top 10 in depth:

Programming language Description Applications 
Python An interpreted, high level, general-purpose programming language.  Typing discipline: Duck, dynamic, gradual  Paradigm: Multi-paradigm, functional, imperative, object-oriented, reflective – Artificial intelligence – embedded microcontrollers 
C++ A high level, general purpose programming language.  Typing discipline: Static, nominative  Paradigm: object oriented, functional – Software infrastructure and resource constrained applications. – Performance-critical applications 
Java A general purpose, class based, object-oriented language.  Typing discipline: Static, strong  Paradigm: multi-paradigm, object oriented, imperative, reflective Client-server web applications 
JavaScript A programming language that conforms to the ECMAScript specification.  Typing discipline: Dynamic, duck  Paradigm: Event-driven, functional, imperative Almost all modern web applications require JavaScript 

We can see that (without counting Java), many other languages have “Functional” in their paradigm style.

Will the rise of Functional Programming kill OOP? 

Java developers love Java and its OOP features. It’s 4 key OOP features are embedded from the time of studying in University through to a career as Java developer: Abstraction, encapsulation, inheritance and polymorphism. Explaining and using them is often deep in a developer’s comfort zone. However, as explained above, the benefits of Functional Programming are undeniable. It was inevitable that Java would adopt Functional Programming, and that is exactly what Oracle had in mind with the introduction of Lambda and Stream support starting with Java 8.

Looking back to the top 10 programming languages by IEEE, most offer multi-paradigm support. They have features for both OOP and Functional Programming. There is no “silver bullet” in the programming paradigm approach – some problems are better solved with OOP and some with Functional Programming. Here is a list the Pros and Cons of OOP vs Functional:

 OOP Functional 
Usage Should be used when there are many things with few operations Should be used when there are few things with many operations 
Execution State can be changed thus unpredictable, especially in race condition Data is immutable thus predictable 
Modeling Inheritance and generics make it easier for modelling, code reusability and testability Limited 
Code readability – Somewhat less verbose – Low level of code instruction; statements specify How – Concise: It takes fewer lines of code which in turn makes it easier to maintain – Free of low-level operations, and less error prone: specify What, and input are immutable 
Performance Relies on multi-thread programming to use multi-core CPUs but leads to race condition problems. Developers must program carefully with synchronize block, notify, and lock mechanisms which are error prone. Low readability makes it hard to maintain or debug Depends on libraries to leverage parallelism to boost performance. 

How Java adapt to Functional Programming? 

Java 8 introduced Functional Programming features with lambdas and streams. Let’s look at them in detail.

Lambda

Lambda expressions can be understood as a concise representation of an anonymous function that can be passed around (2015, Java 8 in Action, Manning):

  • Anonymous: it does not have an explicit name like a method would
  • Function: Lambda expression is not associated with any class
  • Passed around: a lambda expression can be passed as argument to a method or stored in a variable
  • Concise: no more boilerplate code such as variable declaration

Imagine that we have a shopping cart checkout application where some products have a promotional discounts while others do not. The discounts can be different percentages. How can the total price of the shopping cart be calculated?

Using the old method:

BigDecimal total = BigDecimal.Zero; 
for(Product product: shoppingCart.products) { 
    if(product.hasPromotion == true) 
        total += product.price * product.promotion; 
} 

Using the new method:

BigDecimal total = shoppingCart.products 
        .filter(prod -> prod.hasPromotion) 
        .map(prod -> prod.price * prod.promotion) 
        .reduce(BigDecimal.Zero, BigDecimal::Add); 

Using Collection API with Lambda expressions 

Prior to Java 8, the Collections API was often used with a loop control to iterate through the collection. Using this shopping cart example, how can all product inside the shopping cart be printed?

The old way:

for(Product prod : shoppingCart.getAllProducts()) { 
    System.out.println(prod.getName()); 
} 

The new way:

shoppingCard.getAllProducts().foreach(System.out::println); 

The new style is much more readable and concise as the Lambda expression helps us concisely iterate through a collection.

Another benefit of using a Functional Programming style in Java is that the side effect of a mutable input can be avoided. For example, to get a List of all uppercase Product Names in shopping cart, there are 2 choices.

The old way:

List<String> uppercaseProdNames = new ArrayList<>(); 
for(Product prod : shoppingCart.products) { 
    uppercaseProdNames.add(prod.getName().toUpperCase()); 
} 

The problem with this approach is that there is a possibility that the original shoppingCart products may be modified during the iteration. With a Lambda expression, we can use its immutability characteristic to eliminate unpredicted behaviors such as modifying the original input.

The new way:

List<String> uppercaseProdNames = shoppingCart.products 
    .stream() 
    .map(p -> p.getName().toUpperCase()) 
    .collect(Collectors.toList()); 

Data processing using new Stream API 

The above example uses the stream function of the Collection API. The Algorithm and Data Structure are the two most important subjects in computer science, and the Collection API is therefore used heavily by Java Developers. With the introduction of Functional Programming in Java with Lambda, Stream is the update necessary to manipulate Collections API in a functional way. It also helps improve performance by leveraging parallelism without having to explicitly program multi-threading. For example, this code snippet shows how a bike shop application can return list of all bikes with a price greater than $1500, sorted by price descending.

The old way:

List<Bike> expensiveBikes = new ArrayList<>(); 
for(Bike bike: allBikes) { 
    if(bike.getPrice() > 1500) { 
        expensiveBikes.add(bike); 
    } 
} 
 
// Sorting the expensiveBikes list in descending order 
Collections.sort(expensiveBikes, new Comparator<Bike>() { 
    public int compare(Bike b1, Bike b2) { 
        return Integer.compare(b2.getPrice(), b1.getPrice)); 
    } 
}); 
 
// Processing the sorted list to select titles of the bikes 
List<String> expensiveBikeTitles = new ArrayList<>(); 
for(Bike bike: expensiveBikes) { 
    expensiveBikeTitles.add(bike.getTitle()); 
} 

This code has some problems, including:

  • The introducing of the intermediate garbage variable expensiveBikes
  • Too many imperative lines of code that make it hard to follow the logic
  • Sequential loop makes it difficult to introduce concurrent processing to improve performance

To address these problems, in Java 8 we can write:

List<String> expensiveBikeTitles = allBikes.parallelStream() 
        .filter(b -> b.getPrice() > 1500) 
        .sorted(comparing(Bike::getPrice)) 
        .map(Bike::getTitle) 
        .collect(Collectors.toList()); 

The code is much more concise, being written as a problem definition!

What’s Next for Java Developer 

The introduction of Lambda 8 and modifications to the Collection API with Stream were big changes to the Java programming language. Since then, Java has had multi-paradigm support for both OOP and Functional Programming, and Oracle has pushed it further in version 10 with introduction of the inferred type for local variables.

Java Developers can even explore Functional Programming with other languages.

Scala

Scala is a programming language that offers both Object-Oriented and Functional Programming. It is created for developer who has background in Java and want to pursue more of Functional Programming. Scala programs run on a JVM and can also use Java libraries.

Kotlin

Like Scala, Kotlin has both Object-Oriented and Functional features. Kotlin is also targeted for JVMs and can also use Java libraries. But it takes Functional Programming support even further with higher-order functions. A higher-order function is a function that takes functions as parameters.

Groovy

Java Developers can also choose to work with Groovy, another language that supports both OOP and Functional Programming. Apart from being more functional than Java, Groovy also differs from Java in that it supports the dynamic typing discipline.

Conclusion

While Java has adopted Functional-style programming by introducing Lambda, OOP will not be abandoned from Java 8. Knowing pros and cons of both OOP and Functional, developers can mix both styles effectively. The real work object can be modelled with its states and their relationships leveraging Functional Programming for operations to improve code readability and performance. With its multi-paradigm support, Java remains a great option for developing applications.

Share

Get the latest news from us to your inbox

(Weekly newsletter)

Leave a comment



from Indonesia:

from Australia:

from New Zealand:

from Singapore:

from other countries:

© Copyright 1991 - 2020 Mitrais