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 AUGUST 2017

Refactoring to Functional Style in Java 8: Using Functional/Declarative Built-in Functions

By Venkat Subramaniam

PragPub
The Pragmatic Programmers
7 min readAug 24, 2022

--

In this installment of his series, Venkat shows how using built-in functions can reduce the amount of code and make it more expressive.

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

Primitive obsession is a common anti-pattern in programming where a programmer chooses a combination of rudimentary operations to accomplish the task at hand. By looking for built-in functions in the language or the library we can reduce the amount of code and, at the same time, make it expressive as well. In this article we explore this with a common example.

A Code with Primitive Obsession

A number is considered prime if it is greater than 1 and is not divisible by any numbers other than 1 and itself. Here is a piece of code, written in imperative style, to find if a number is prime.

Imperative Solution

public static boolean isPrime(int number) {
boolean divisible = false;
for(int i = 2; i <= Math.sqrt(number); i++) {
if(number % i == 0) {
divisible = true;
break;
}
}
return number > 1 && !divisible;
}

The code exhibits the smell of primitive obsession — we take full control of all the details: first initialize a garbage variable — a flag — named divisible, then set up the loop index variable i, set the bounds and the loop increment. Within the loop, decide if we should continue with the loop or break out. Furthermore, we have to remember to set the flag variable appropriately.

The code is very familiar, but has a lot of moving parts. Instead, programming tasks like this using the declarative and functions style can make the code more expressive, concise, and easier to read as well. Before we refactor this code to functional style, let’s look at some built-in convenience functions that will support our effort.

Range Instead of Looping

The traditional for loop gives us the ability to iterate over a range of values, evaluating a piece of code in the context of an index variable. Here’s a piece of code that iterates from 1 to 3:

Looping

for(int i = 1; i <= 3; i++) { 
System.out.println(i);
}

The code is interested in iterating over a range of values, we can capture that intention directly with the rangeClosed method of IntStream, like so:

Iterating Over a Range

IntStream.rangeClosed(1, 3)
.forEach(i -> System.out.println(i));

IntStream is part of the java.util.stream package in JDK 8. Its rangeClosed method is an internal iterator from the first value to the second value given as argument. If we want to stop one shy of the second argument, for example, stop at 2 instead of 3 in the example, we can use the range method instead of the rangeClosed method.

Filtering Values in a Range

In the previous example we printed the values, that is, we operated on each value in the range. Since range and rangeClosed return internal iterators, we can easily skip values and pick specific values we’re interested in operating on.

For example, if we want to print only the even values in a range, here’s the code for that:

Filtering Values in a Range

IntStream.rangeClosed(1, 10)
.filter(i -> i % 2 == 0)
.forEach(i -> System.out.println(i));

Instead of operating on every value in the range, we picked only the even values or values that satisfied the predicate given to the filter method, and then operated only on those.

Truth Over a Range

When working with a collection of values, whether from a range or an arbitrary collection, we’re often interested if a particular condition or statement applies across all the values. For example, given a group of friends, you may be interested in knowing:

  • are all friends available to hang out on Friday evening
  • is any friend available
  • is no one available

Both the Stream and IntStream in JDK 8 support methods that can easily provide us these details.

• The allMatch method will return true if each and every value satisfies the given predicate. If at least one of the values does not satisfy, then the result is false.

• The anyMatch method will return true if at least one of the values satisfies the given predicate. If none of the values satisfy, then the result is false.

• The noneMatch method will return true if no value satisfies the given predicate. If at least one of the values satisfies, then the result is false.

Instead of mundanely iterating over each and every value and performing the check on individual values in the collection or range, we can use these functions at the collection or range level to more fluently accomplish the task.

Laziness and Short Circuiting

Fluency is a very desirable quality, but we rarely want that instead of performance. If we loop through manually, we can control the looping using break, continue, and such. We want to make sure the fluent declarative and functional code does not result in poorer performance.

The evaluation of the predicates are lazy and the methods use short circuiting. Suppose we have a collection of values between 1 and 100,000. Now, if we ask pass to allMatch a predicate that examines if the given value is less than 5— that is, we want to know if each and every value is less than 5. It would be rather inefficient if each and every one of the 100,000 values are evaluated. Thankfully, that’s not the case. The instance one of the values fails the check allMatch will return false and will not perform any extra computations. We can see this in the following example.

Filtering Values in a Range

System.out.println( 
IntStream.rangeClosed(1, 100000)
.peek(i -> System.out.println("Evaluating " + i))
.allMatch(i -> i < 5));

The peek method is a debugging tool that lets us peek into the stream of values, right in the middle of the iteration. The predicate passed to the allMatch method is asking if a given value is less than 5. In the given range, all values 5 and later do not make that cut. Instead of evaluating each value, the evaluation breaks out of the iteration upon seeing the first failure, as we see in the output:

Evaluating 1
Evaluating 2
Evaluating 3
Evaluating 4
Evaluating 5
false

Refactoring To Functional Style

Let’s revisit the code we started with — the isPrime method. To identify the declarative and functional style, let’s state the problem at hand once again: A number is considered prime if it is greater than 1 and if it is not divisible by any number between 2 and the number - 1. We can express the first part of that sentence using the range function and the second part using the noneMatch method.

Here’s the refactored version of the isPrime method:

Refactored method

public static boolean isPrime(int number) {
return number > 1 &&
IntStream.range(2, number)
.noneMatch(i -> number % i == 0);
}

Since we want the range of values from 2 but one less than the given number, the method of choice to iterate is range instead of rangeClosed. Alternately we may call rangeClose but pass number - 1 as the second argument. Since the goal is to check that no number in that range divides the given number, noneMatch is our pick instead of allMatch or anyMatch. If we used either of those methods we would have to use negation in the logic, using noneMatch is simpler.

While this refactoring example was specific to the use of some methods of IntStream, we can extrapolate a general approach from this sample. Given a problem, to avoid primitive obsession state the problem in words and then look for built-in functions that will step us to declaratively and functionally implement the stages or steps in the stated problem.

Conclusion

Instead of manually iterating over values, we can make use of the range or rangeClosed methods of IntStream. Also, to check if all, any, or none of the values in a collection or range meet certain criteria we can use methods like allMatch, anyMatch, and noneMatch. In general, we can avoid primitive obsession and low level imperative code by looking for built-in functions that promote declarative and functional style.

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 from PragPub Magazine, August 2017 featuring a rock climber
Cover from PragPub Magazine, August 2017

--

--

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).