Take a step back in history with the archives of PragPub magazine. The Pragmatic Programmers hope you’ll find that learning about the past can help you make better decisions for the future.

FROM THE ARCHIVES OF PRAGPUB MAGAZINE JULY 2019

Refactoring to Functional Style in Java 8: From Legacy to Lambdas

By Venkat Subramaniam

PragPub
The Pragmatic Programmers
10 min readJul 15, 2022

--

Java 8 added functional programming capabilities, but when it comes to really writing Java code in a functional style, you may need a guide. Like Venkat.

https://pragprog.com/newsletter/
https://pragprog.com/newsletter/

In Java, we’ve been writing object-oriented code using the imperative style. Starting with Java 8, we can write code in functional style. While it does not make sense to convert all existing code, whenever we refactor existing code— while fixing bugs or making enhancements — we can make use of the functional style. In this new series of articles, we will learn why and how. In this article, we will see how and where we can use lambdas directly in legacy code.

Why Functional Style?

In imperative style, we tell the code what to do and how to do it. In declarative style, we focus on telling what and let the underlying libraries take care of the how. Functional style is declarative and makes use of higher order functions, that is, the ability to pass around functions like we’re used to passing around objects.

Imperative style often involves finely grained details and mutability. That makes the code more complex to write. Even though functional style is not as familiar as the imperative style, to a lot of us programming in Java, functional style code is inherently less complex in comparison.

Once we get comfortable with the syntax and the concepts, we’ll find that the code is more concise and expressive compared to its imperative counterpart.

Lambdas and Java

Lambda expressions, or just lambdas, have been around in other languages for a few decades. They are new to Java, though, introduced in Java 8. They can be understood by comparison with functions.

A function has four parts:

  1. the return type
  2. a name
  3. the parameter list
  4. the body

Lambda expressions only have the last two — parameter list and body — separated by ->. Lambdas distill down functions to the essence. Here’s an example lambda expression that takes an Integer value and returns its double:

(Integer number) -> number * 2

Java can infer the type of the parameters based on the context and we can also drop the parenthesis for single parameters. Thus, we can reduce some clutter from the above lambda expression, like so:

number -> number * 2

In addition to introducing lambda expressions to Java, the language architects have made it possible to use lambdas with legacy code that has not been recompiled to Java 8. We explore that aspect in this article.

Lambdas Instead of Runnable

Let’s take a simple piece of code that makes use of a class created more than 20 years ago.

public class UsingThread {
public static void main(String[] args) {
Thread thread =
new Thread(new Runnable() {
public void run() {
System.out.println("Hello from another thread");
}
});
thread.start();
System.out.println("Hello from main");
}
}

In the main() method we create an instance of Thread and pass to it an anonymous inner class that implements the Runnable interface. When run, the program produces the output:

Hello from main
Hello from another thread

Looking at the code, we passed an object to the constructor of Thread; but our main intention is to pass a function, a piece of executable code, for invocation in another thread. In short, we wanted to pass a function to the constructor.

In Java, we have been treating functions like they are pre-schoolers. “Can I go to the neighborhood park?” “No sweetie, you can’t go alone, let me send an adult over with you.” We’ve been wrapping functions as single methods into classes so we can pass them around. That ceremony ends with Java 8.

The constructor of the Thread class has not changed in Java 8. It’s been the same for a few decades, but we can use it differently in Java 8. Let’s rewrite the code above to use lambda expressions.

public class UsingThread {
public static void main(String[] args) {
Thread thread =
new Thread(new Runnable() {
public void run() {
System.out.println("Hello from another thread");
}
});
thread.start();
System.out.println("Hello from main")
}
}

Instead of creating an object, an anonymous inner class, that implements the run method of the Runnable interface, we passed to the good-old constructor a lambda expression. The lambda expression does not have a name, it’s anonymous, but it faithfully stands in for the run method that the Thread class expects. With lambda expressions, Java 8 places importance on method signature — the types of the parameters and the return value — instead of the name of the method. A lambda expression that has a compatible signature can stand in for the abstract method of a single abstract method interface.

Lambdas Can Stand In For Functional Interfaces

Unlike other languages, Java does not provide function objects to represent lambda expressions. At first sight, the fact that we can assign a lambda expression to an interface may appear weird, but there’s a good reason for it— backward compatibility.

In Java 8, we can pass an anonymous function, a lambda expression, anywhere a single abstract method interface is expected. These interfaces are called Functional Interfaces. Anywhere interfaces such as Runnable, Callable, FileFilter, and so on, or our own homegrown single abstract method interfaces are expected, we can pass lambda expressions. That’s the reason why we were able to pass a lambda expression to the constructor of the Thread class.

Let’s look at another example; this time where we can pass a lambda expression where Callable is expected. Let’s start with the code that uses Callable.

import java.util.*;
import java.util.concurrent.*;
#
public class StockPrices {
public static String getPriceFor(String ticker) {
return ticker + " : " + YahooFinance.getPrice(ticker);
}
#
public static void main(String[] args) {
try {
List<String> tickers =
Arrays.asList("GOOG", "AMZN", "AAPL",
"MSFT", "INTC", "ORCL");
#
ExecutorService executorService = Executors.newFixedThreadPool(100);
#
List<Future<String>> pricesFutures = new ArrayList<>();
#
for (String ticker: tickers) {
pricesFutures.add(executorService.submit(new Callable<String> () {
public String call() {
return getPriceFor(ticker);
}
}));
}
#
for (Future<String> priceFuture: pricesFutures) {
System.out.println(priceFuture.get());
}
#
executorService.shutdown();
executorService.awaitTermination(100, TimeUnit.SECONDS);
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
}
}

The getPriceFor() method takes a String representing a ticker as parameter and returns a String of the ticker name and the price for it. Internally, it calls YahooFinance’s getPrice() method — we’ll meet this class soon. In the main() method we create an ExecutorService of a pool of threads, submit tasks to fetch each stock’s price. Then we get the data from the Future returned by the submit() method of the ExecutorService. Finally, we shut down the pool and wait for its termination.

Here’s the YahooFinance class:

import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import static java.util.stream.Collectors.toList;
#
public class YahooFinance {
public static double getPrice(final String ticker) {
try {
final URL url =
new URL("http://ichart.finance.yahoo.com/table.csv?s=" +
ticker);
final BufferedReader reader =
new BufferedReader(new InputStreamReader(url.openStream()));
final String[] dataItems =
reader.lines().collect(toList()).get(1).split(",");
double price =
Double.parseDouble(dataItems[dataItems.length - 1]);
return price;
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}

The getPrice() method sends a request to the Yahoo! URL that provides a stock’s data, parses the result, extracts, and returns the desired price value.

When the StockPrices class is executed, it produces a result like:

GOOG : 790.799988
AMZN : 757.77002
AAPL : 115.970001
MSFT : 62.299999
INTC : 36.310001
ORCL : 39.099998

The actual price displayed will vary, depending on the market.

Let’s focus on the main() method of the StockPrices class. We’re yet again passing an anonymous inner class that implements a Functional Interface. Let’s rework the example to use lambda expressions.

In the main() method, we can rewrite the part:

for (String ticker: tickers) {
pricesFutures.add(executorService.submit(new Callable<String> () {
public String call() {
return getPriceFor(ticker);
}
}));
}

as

for (String ticker: tickers) {
pricesFutures.add(executorService.submit(() ->
getPriceFor(ticker)));
}

That reduced some clutter, no need for all that new, public, return, etc. Sending a lambda expression where a Functional Interface is expected is a first good step, but we can do a lot better.

Converting From an External to an Internal Iterator

Instead of using the for loop, which is an external iterator — where we control the iteration — we can use an internal iterator — where we put the iteration on autopilot. Let’s rewrite the part:

List<Future<String>> pricesFutures = new ArrayList<>();
for (String ticker: tickers) {
pricesFutures.add(executorService.submit(() ->
getPriceFor(ticker)));
}

Using Java 8 Stream which provides an internal iterator, like so:

List<Future<String>> pricesFutures =
tickers.stream()
.map(ticker -> executorService.submit(() ->
getPriceFor(ticker)))
.collect(toList());

Well, this version does not appear to be more concise than the imperative style. Furthermore, converting the next for loop, the one that invokes get to an internal iterator will prove to be difficult. The get method of Future may throw an InterruptedException or an ExecutionException, both of which are checked exceptions. Writing:

//won't work 
pricesFutures.forEach(priceFuture ->
System.out.println(priceFuture.get()));

… will not work. We’ll get a compilation error:

error: unreported exception InterruptedException;
must be caught or declared to be thrown

What gives?

Using Parallel Streams

Converting anonymous inner classes to lambda expressions may reduce clutter and make the code concise in some cases, like in the first example we saw. It also reduces clutter in the ExecutorService example, but in the context of the entire code, the savings or benefit seems rather insignificant in that example.

Sometimes we should look for a simple change, replacing instances that implement a Functional Interface with lambda expressions. At other times, we have to consider an entirely different approach. The ExecutorService example requires such a measure.

A quick look at the problem at hand tells us that taking a list of ticker symbols and finding their prices concurrently is our objective. We can use parallel streams for this purpose — see the book Functional Programming in Java for more details. Let’s rewrite the entire main() method to make better use of this Java 8 facility.

public static void main(String[] args) {
List<String> tickers =
Arrays.asList("GOOG", "AMZN", "AAPL",
"MSFT", "INTC", "ORCL");
tickers.parallelStream()
.map(StockPrices::getPriceFor)
.forEachOrdered(System.out::println);
}

Nope, we’re not missing any code, that’s the entire main() function — 18 lines reduced to 3 lines.

We iterate over the tickers list, in parallel, fetch the stock prices, and print them in the order in which the symbols appear in the list. If we do not care about the order, we may use forEach instead of forEachOrdered.

By default, the parallel stream version uses as many threads as the number of cores on the runtime system. However, the ExecutorService version controlled the number of threads more explicitly. We can do the same with the parallel stream as well.

We can control JVM wide the number of threads in the pool for the parallel stream by using the runtime option:

-Djava.util.concurrent.ForkJoinPool.common.parallelism=100

For example, to use this option, after compiling the parallel stream version of the StockPrices class, run it as follows:

java -Djava.util.concurrent.ForkJoinPool.common.parallelism=100  
StockPrices

We can also control the number of threads programmatically. Here’s a modified version of StockPrices’s main() method that provides 100 threads in the pool for execution of the parallel stream.

public static void main(String[] args) {
try {
List<String> tickers =
Arrays.asList("GOOG", "AMZN", "AAPL",
"MSFT", "INTC", "ORCL");
ForkJoinPool pool = new ForkJoinPool(100);
pool.submit(() ->
tickers.parallelStream()
.map(StockPrices::getPriceFor)
.forEachOrdered(System.out::println));
pool.shutdown();
pool.awaitTermination(100, TimeUnit.SECONDS);
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
}

We created a ForkJoinPool with 100 threads and invoked the parallel stream from within the code running in the execution context of that pool of threads. The parallel stream will now use the threads from that pool instead of the default.

In this article we saw how we can start by using lambda expressions where a Functional Interface — one that has a single abstract method — is expected. This can be a good first step as we begin to refactor existing code while fixing bugs or making enhancements. Functional style code, in general, is more concise, expressive, and easier to understand once we get comfortable with the syntax and the concepts. In addition to this first step of refactoring, we also saw how some of the more powerful functions in the Java 8 JDK may be used to reimplement code to make it further more concise and expressive. In the next article in this series, we will discuss how looking at the whole instead of parts can help us refactor the code to functional style more easily.

And when you’re ready to dive in, there’s my book:

About Venkat Subramaniam

Dr. Venkat Subramaniam is an award-winning author, founder of Agile Developer, Inc., and an instructional professor at the University of Houston. He has trained and mentored thousands of software developers in the U.S., Canada, Europe, and Asia, and is a regularly invited speaker at several international conferences. Venkat helps his clients effectively apply and succeed with agile practices on their software projects. Venkat is a (co)author of multiple books, including the 2007 Jolt Productivity award-winning book Practices of an Agile Developer.

Cover of PragPub magazine, July 2019
Cover of PragPub magazine, July 2019

--

--

PragPub
The Pragmatic Programmers

The Pragmatic Programmers bring you archives from PragPub, a magazine on web and mobile development (by editor Michael Swaine, of Dr. Dobb’s Journal fame).